Genesis.
This commit is contained in:
commit
365ad5a560
95 changed files with 13240 additions and 0 deletions
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
*~
|
||||
*.o
|
||||
*.lo
|
||||
*.la
|
||||
.deps
|
||||
.libs
|
||||
ABOUT-NLS
|
||||
aclocal.m4
|
||||
auto-config.h
|
||||
auto-config.h.in
|
||||
autom4te.cache
|
||||
config.log
|
||||
config.status
|
||||
configure
|
||||
curlpp/
|
||||
libtool
|
||||
m4
|
||||
makefile
|
||||
makefile.in
|
||||
quote/
|
||||
stamp-h1
|
||||
TAGS
|
||||
test-driver
|
||||
trader-desk.pc
|
||||
trader-desk/trader-desk
|
||||
11
AUTHORS
Normal file
11
AUTHORS
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
Dale Mellor
|
||||
|
||||
|
||||
______________________________________________________________________
|
||||
Copyright (c) 2017 Dale Mellor
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. This file is offered as-is,
|
||||
without any warranty.
|
||||
|
||||
0
COPYING
Normal file
0
COPYING
Normal file
26
ChangeLog
Normal file
26
ChangeLog
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
2017-02-10 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* configure.ac (AC_CONFIG_FILES): Add po/Makefile.in.
|
||||
|
||||
2017-02-10 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* configure.ac (AC_CONFIG_FILES): Add po/Makefile.in.
|
||||
|
||||
2017-02-09 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* configure.ac (AM_GNU_GETTEXT_VERSION): Bump to 0.19.4.
|
||||
|
||||
2017-02-09 gettextize <bug-gnu-gettext@gnu.org>
|
||||
|
||||
* configure.ac (AM_GNU_GETTEXT_VERSION): Bump to 0.19.3.
|
||||
|
||||
|
||||
|
||||
|
||||
______________________________________________________________________
|
||||
Copyright (c) 2017 Dale Mellor
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. This file is offered as-is,
|
||||
without any warranty.
|
||||
25
INSTALL
Normal file
25
INSTALL
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
* Build instructions
|
||||
|
||||
*** From a GIT checkout
|
||||
|
||||
First, cd into the top-level source directory, then type
|
||||
|
||||
autoreconf --install && ./configure --prefix=$PWD/install && \
|
||||
make install
|
||||
|
||||
(you can of course run each piece separately), after which the program will
|
||||
be installed at install/bin/cosmosis.
|
||||
|
||||
|
||||
If you make subsequent changes in the code base, you only need to run
|
||||
|
||||
make && make install
|
||||
|
||||
to have your changes implemented in the installation.
|
||||
|
||||
|
||||
*** From a tarball
|
||||
|
||||
Unpack the tar file and cd into the top directory, then type
|
||||
|
||||
./configure --prefix=$PWD/install && make install
|
||||
13
NEWS
Normal file
13
NEWS
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
2017-11-03 Planned first release to public.
|
||||
|
||||
2007-11-03 Development begins in earnest.
|
||||
|
||||
|
||||
|
||||
______________________________________________________________________
|
||||
Copyright (c) 2017 Dale Mellor
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. This file is offered as-is,
|
||||
without any warranty.
|
||||
1
README
Symbolic link
1
README
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
README.md
|
||||
117
README.md
Normal file
117
README.md
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
> Github is a secondary distribution point for this project; please see
|
||||
> https://rdmp.org/dmbcs/trader-desk to be sure to see the most up to date
|
||||
> version and complete details.
|
||||
|
||||
# DMBCS Trader-Desk Application
|
||||
|
||||
> This is a beta release of C++ code designed to be built and run on a
|
||||
> Gnu/Linux-based system.
|
||||
|
||||
While stock charting applications are ten a penny, it seems like every
|
||||
time you look at a different stock chart you see a completely different
|
||||
picture of the situation. Especially when it is your broker: things
|
||||
always look great until the time you buy in and then you see things are
|
||||
all really downhill.
|
||||
|
||||
So we want a desk which shows us data we can believe, and allows us to
|
||||
very quickly view and analyze the data in lots of ways, at lots of time
|
||||
scales; we want to be live and interactive with our data.
|
||||
|
||||
Specifically, our requirements are
|
||||
|
||||
* Show full, at-a-glance, view of an entire market
|
||||
* Instantly bring up all the details of any one company
|
||||
* Have all data (ten years, all markets) on local machine, in a
|
||||
proper database, for speed
|
||||
* Application must be lightning fast, we want to pan and
|
||||
zoom around the data to our heart’s content
|
||||
* Ultimately we want trading robots which can react to
|
||||
events super-fast
|
||||
* Provide plug-in infrastructure for analysis modules, and
|
||||
ultimately for robot traders
|
||||
|
||||
## The Project
|
||||
|
||||
We are a team of professional C++ Linux programmers and this has been a
|
||||
personal side project, and thus isn’t really designed for portability or
|
||||
ease of installation. But we have made an effort, and the application
|
||||
does include a wizard which will go some way towards getting your database
|
||||
set up correctly.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
The requirements are specific and quite hard:
|
||||
|
||||
* `gcc 9.3`
|
||||
* Yep, this is written to C++20 standards with **concepts** and the
|
||||
**fmt** library in use.
|
||||
* `fmt`
|
||||
* `mariadb`
|
||||
* `dmbcs-market-data-api`
|
||||
* An account at *AlphaVantage* (free); you will need to acquire an *API
|
||||
key* from that site and enter it at the bottom of the preferences dialog
|
||||
box in the `trader-desk` application
|
||||
|
||||
It has only been built on a Debian 10 (current stable) system. The file
|
||||
`setup-hint.sh` at the root of the distribution is a pseudo-script for
|
||||
configuring and installing into a clean-built Debian 10 machine. It is
|
||||
not expected that you would just run this blindly, but use it as a guide
|
||||
for manually setting your own system up at the command line; in particular
|
||||
it will inform you how to get the dependencies listed above.
|
||||
|
||||
|
||||
## Download
|
||||
|
||||
The `dmbcs-trader-desk` source code is managed with *GIT* (configured with
|
||||
*autotools*, built with *make* and a good C++20 compiler). Type
|
||||
|
||||
> `git clone http://rdmp.org/dmbcs/trader-desk.git dmbcs-trader-desk`
|
||||
|
||||
at the command line to obtain a copy.
|
||||
|
||||
This repository also comes with some database pre-population data, so that
|
||||
you will have something interesting to look at as soon as you start the
|
||||
application running!
|
||||
|
||||
## Documentation
|
||||
|
||||
As per above, build and installation instructions take the form of the
|
||||
`setup-hint.sh` pseudo-script included with the sources. The
|
||||
source is about 50% covered by *Doxygen* notes in the header files.
|
||||
|
||||
Real end-user documentation is non-existent right now. You should have
|
||||
had a look at the video above, and then you will be able to follow your
|
||||
instincts and find your own way around the application.
|
||||
|
||||
## Contact
|
||||
|
||||
Please click [here](https://rdmp.org/dmbcs/contact) if you wish to send us
|
||||
a message.
|
||||
|
||||
### Mailing list
|
||||
|
||||
If you would like to receive e-mail notices of matters arising about this
|
||||
application, you may request this through the contact form above.
|
||||
|
||||
### Contribution to development
|
||||
|
||||
We will happily consider contributions to the source code if you provide
|
||||
the address of a GIT repository we can pull from, or send a pull request
|
||||
via Github, and will consider all bug reports and feature requests.
|
||||
|
||||
## Donations
|
||||
|
||||
If you use this application please consider a bitcoin donation if you can.
|
||||
A small amount informs us that there is interest and that we are providing
|
||||
a useful service to the community; it will keep us motivated to continue
|
||||
to make open source software. Donations can be made by bitcoin to the
|
||||
address `1PWHez4zT2xt6PoyuAwKPJsgRznAKwTtF9`.
|
||||
|
||||
|
||||
______________________________________________________________________
|
||||
Copyright (c) 2017, 2020 Dale Mellor
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. This file is offered as-is,
|
||||
without any warranty.
|
||||
8
build-aux/.gitignore
vendored
Normal file
8
build-aux/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
compile
|
||||
config.guess
|
||||
config.rpath
|
||||
config.sub
|
||||
depcomp
|
||||
install-sh
|
||||
ltmain.sh
|
||||
missing
|
||||
287
build-aux/gettext.h
Normal file
287
build-aux/gettext.h
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/* Convenience header for conditional use of GNU <libintl.h>.
|
||||
Copyright (C) 1995-1998, 2000-2002, 2004-2006, 2009-2011 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
#ifndef _LIBGETTEXT_H
|
||||
#define _LIBGETTEXT_H 1
|
||||
|
||||
/* NLS can be disabled through the configure --disable-nls option. */
|
||||
#if ENABLE_NLS
|
||||
|
||||
/* Get declarations of GNU message catalog functions. */
|
||||
# include <libintl.h>
|
||||
|
||||
/* You can set the DEFAULT_TEXT_DOMAIN macro to specify the domain used by
|
||||
the gettext() and ngettext() macros. This is an alternative to calling
|
||||
textdomain(), and is useful for libraries. */
|
||||
# ifdef DEFAULT_TEXT_DOMAIN
|
||||
# undef gettext
|
||||
# define gettext(Msgid) \
|
||||
dgettext (DEFAULT_TEXT_DOMAIN, Msgid)
|
||||
# undef ngettext
|
||||
# define ngettext(Msgid1, Msgid2, N) \
|
||||
dngettext (DEFAULT_TEXT_DOMAIN, Msgid1, Msgid2, N)
|
||||
# endif
|
||||
|
||||
#else
|
||||
|
||||
/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which
|
||||
chokes if dcgettext is defined as a macro. So include it now, to make
|
||||
later inclusions of <locale.h> a NOP. We don't include <libintl.h>
|
||||
as well because people using "gettext.h" will not include <libintl.h>,
|
||||
and also including <libintl.h> would fail on SunOS 4, whereas <locale.h>
|
||||
is OK. */
|
||||
#if defined(__sun)
|
||||
# include <locale.h>
|
||||
#endif
|
||||
|
||||
/* Many header files from the libstdc++ coming with g++ 3.3 or newer include
|
||||
<libintl.h>, which chokes if dcgettext is defined as a macro. So include
|
||||
it now, to make later inclusions of <libintl.h> a NOP. */
|
||||
#if defined(__cplusplus) && defined(__GNUG__) && (__GNUC__ >= 3)
|
||||
# include <cstdlib>
|
||||
# if (__GLIBC__ >= 2 && !defined __UCLIBC__) || _GLIBCXX_HAVE_LIBINTL_H
|
||||
# include <libintl.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* Disabled NLS.
|
||||
The casts to 'const char *' serve the purpose of producing warnings
|
||||
for invalid uses of the value returned from these functions.
|
||||
On pre-ANSI systems without 'const', the config.h file is supposed to
|
||||
contain "#define const". */
|
||||
# undef gettext
|
||||
# define gettext(Msgid) ((const char *) (Msgid))
|
||||
# undef dgettext
|
||||
# define dgettext(Domainname, Msgid) ((void) (Domainname), gettext (Msgid))
|
||||
# undef dcgettext
|
||||
# define dcgettext(Domainname, Msgid, Category) \
|
||||
((void) (Category), dgettext (Domainname, Msgid))
|
||||
# undef ngettext
|
||||
# define ngettext(Msgid1, Msgid2, N) \
|
||||
((N) == 1 \
|
||||
? ((void) (Msgid2), (const char *) (Msgid1)) \
|
||||
: ((void) (Msgid1), (const char *) (Msgid2)))
|
||||
# undef dngettext
|
||||
# define dngettext(Domainname, Msgid1, Msgid2, N) \
|
||||
((void) (Domainname), ngettext (Msgid1, Msgid2, N))
|
||||
# undef dcngettext
|
||||
# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
|
||||
((void) (Category), dngettext (Domainname, Msgid1, Msgid2, N))
|
||||
# undef textdomain
|
||||
# define textdomain(Domainname) ((const char *) (Domainname))
|
||||
# undef bindtextdomain
|
||||
# define bindtextdomain(Domainname, Dirname) \
|
||||
((void) (Domainname), (const char *) (Dirname))
|
||||
# undef bind_textdomain_codeset
|
||||
# define bind_textdomain_codeset(Domainname, Codeset) \
|
||||
((void) (Domainname), (const char *) (Codeset))
|
||||
|
||||
#endif
|
||||
|
||||
/* Prefer gnulib's setlocale override over libintl's setlocale override. */
|
||||
#ifdef GNULIB_defined_setlocale
|
||||
# undef setlocale
|
||||
# define setlocale rpl_setlocale
|
||||
#endif
|
||||
|
||||
/* A pseudo function call that serves as a marker for the automated
|
||||
extraction of messages, but does not call gettext(). The run-time
|
||||
translation is done at a different place in the code.
|
||||
The argument, String, should be a literal string. Concatenated strings
|
||||
and other string expressions won't work.
|
||||
The macro's expansion is not parenthesized, so that it is suitable as
|
||||
initializer for static 'char[]' or 'const char[]' variables. */
|
||||
#define gettext_noop(String) String
|
||||
|
||||
/* The separator between msgctxt and msgid in a .mo file. */
|
||||
#define GETTEXT_CONTEXT_GLUE "\004"
|
||||
|
||||
/* Pseudo function calls, taking a MSGCTXT and a MSGID instead of just a
|
||||
MSGID. MSGCTXT and MSGID must be string literals. MSGCTXT should be
|
||||
short and rarely need to change.
|
||||
The letter 'p' stands for 'particular' or 'special'. */
|
||||
#ifdef DEFAULT_TEXT_DOMAIN
|
||||
# define pgettext(Msgctxt, Msgid) \
|
||||
pgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
|
||||
#else
|
||||
# define pgettext(Msgctxt, Msgid) \
|
||||
pgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
|
||||
#endif
|
||||
#define dpgettext(Domainname, Msgctxt, Msgid) \
|
||||
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, LC_MESSAGES)
|
||||
#define dcpgettext(Domainname, Msgctxt, Msgid, Category) \
|
||||
pgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, Category)
|
||||
#ifdef DEFAULT_TEXT_DOMAIN
|
||||
# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
|
||||
npgettext_aux (DEFAULT_TEXT_DOMAIN, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#else
|
||||
# define npgettext(Msgctxt, Msgid, MsgidPlural, N) \
|
||||
npgettext_aux (NULL, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#endif
|
||||
#define dnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
|
||||
npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#define dcnpgettext(Domainname, Msgctxt, Msgid, MsgidPlural, N, Category) \
|
||||
npgettext_aux (Domainname, Msgctxt GETTEXT_CONTEXT_GLUE Msgid, Msgid, MsgidPlural, N, Category)
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
pgettext_aux (const char *domain,
|
||||
const char *msg_ctxt_id, const char *msgid,
|
||||
int category)
|
||||
{
|
||||
const char *translation = dcgettext (domain, msg_ctxt_id, category);
|
||||
if (translation == msg_ctxt_id)
|
||||
return msgid;
|
||||
else
|
||||
return translation;
|
||||
}
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
npgettext_aux (const char *domain,
|
||||
const char *msg_ctxt_id, const char *msgid,
|
||||
const char *msgid_plural, unsigned long int n,
|
||||
int category)
|
||||
{
|
||||
const char *translation =
|
||||
dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
|
||||
if (translation == msg_ctxt_id || translation == msgid_plural)
|
||||
return (n == 1 ? msgid : msgid_plural);
|
||||
else
|
||||
return translation;
|
||||
}
|
||||
|
||||
/* The same thing extended for non-constant arguments. Here MSGCTXT and MSGID
|
||||
can be arbitrary expressions. But for string literals these macros are
|
||||
less efficient than those above. */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if (((__GNUC__ >= 3 || __GNUG__ >= 2) && !defined __STRICT_ANSI__) \
|
||||
/* || __STDC_VERSION__ >= 199901L */ )
|
||||
# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 1
|
||||
#else
|
||||
# define _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS 0
|
||||
#endif
|
||||
|
||||
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define pgettext_expr(Msgctxt, Msgid) \
|
||||
dcpgettext_expr (NULL, Msgctxt, Msgid, LC_MESSAGES)
|
||||
#define dpgettext_expr(Domainname, Msgctxt, Msgid) \
|
||||
dcpgettext_expr (Domainname, Msgctxt, Msgid, LC_MESSAGES)
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
dcpgettext_expr (const char *domain,
|
||||
const char *msgctxt, const char *msgid,
|
||||
int category)
|
||||
{
|
||||
size_t msgctxt_len = strlen (msgctxt) + 1;
|
||||
size_t msgid_len = strlen (msgid) + 1;
|
||||
const char *translation;
|
||||
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
char msg_ctxt_id[msgctxt_len + msgid_len];
|
||||
#else
|
||||
char buf[1024];
|
||||
char *msg_ctxt_id =
|
||||
(msgctxt_len + msgid_len <= sizeof (buf)
|
||||
? buf
|
||||
: (char *) malloc (msgctxt_len + msgid_len));
|
||||
if (msg_ctxt_id != NULL)
|
||||
#endif
|
||||
{
|
||||
memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
|
||||
msg_ctxt_id[msgctxt_len - 1] = '\004';
|
||||
memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
|
||||
translation = dcgettext (domain, msg_ctxt_id, category);
|
||||
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
if (msg_ctxt_id != buf)
|
||||
free (msg_ctxt_id);
|
||||
#endif
|
||||
if (translation != msg_ctxt_id)
|
||||
return translation;
|
||||
}
|
||||
return msgid;
|
||||
}
|
||||
|
||||
#define npgettext_expr(Msgctxt, Msgid, MsgidPlural, N) \
|
||||
dcnpgettext_expr (NULL, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
#define dnpgettext_expr(Domainname, Msgctxt, Msgid, MsgidPlural, N) \
|
||||
dcnpgettext_expr (Domainname, Msgctxt, Msgid, MsgidPlural, N, LC_MESSAGES)
|
||||
|
||||
#ifdef __GNUC__
|
||||
__inline
|
||||
#else
|
||||
#ifdef __cplusplus
|
||||
inline
|
||||
#endif
|
||||
#endif
|
||||
static const char *
|
||||
dcnpgettext_expr (const char *domain,
|
||||
const char *msgctxt, const char *msgid,
|
||||
const char *msgid_plural, unsigned long int n,
|
||||
int category)
|
||||
{
|
||||
size_t msgctxt_len = strlen (msgctxt) + 1;
|
||||
size_t msgid_len = strlen (msgid) + 1;
|
||||
const char *translation;
|
||||
#if _LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
char msg_ctxt_id[msgctxt_len + msgid_len];
|
||||
#else
|
||||
char buf[1024];
|
||||
char *msg_ctxt_id =
|
||||
(msgctxt_len + msgid_len <= sizeof (buf)
|
||||
? buf
|
||||
: (char *) malloc (msgctxt_len + msgid_len));
|
||||
if (msg_ctxt_id != NULL)
|
||||
#endif
|
||||
{
|
||||
memcpy (msg_ctxt_id, msgctxt, msgctxt_len - 1);
|
||||
msg_ctxt_id[msgctxt_len - 1] = '\004';
|
||||
memcpy (msg_ctxt_id + msgctxt_len, msgid, msgid_len);
|
||||
translation = dcngettext (domain, msg_ctxt_id, msgid_plural, n, category);
|
||||
#if !_LIBGETTEXT_HAVE_VARIABLE_SIZE_ARRAYS
|
||||
if (msg_ctxt_id != buf)
|
||||
free (msg_ctxt_id);
|
||||
#endif
|
||||
if (!(translation == msg_ctxt_id || translation == msgid_plural))
|
||||
return translation;
|
||||
}
|
||||
return (n == 1 ? msgid : msgid_plural);
|
||||
}
|
||||
|
||||
#endif /* _LIBGETTEXT_H */
|
||||
91
configure.ac
Normal file
91
configure.ac
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# -*- Autoconf -*-
|
||||
|
||||
|
||||
# Copyright (c) 2017, 2020 Dale Mellor
|
||||
#
|
||||
# This file is part of the trader-desk package.
|
||||
#
|
||||
# The trader-desk package is free software: you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# The trader-desk package is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
|
||||
# Process this file with autoconf to produce a configure script.
|
||||
|
||||
|
||||
AC_PREREQ(2.69)
|
||||
AC_INIT([trader-desk], [0.1], [dale@rdmp.org])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AM_INIT_AUTOMAKE([silent-rules subdir-objects])
|
||||
AM_SILENT_RULES([yes])
|
||||
AC_CONFIG_SRCDIR([trader-desk/trader-desk.cc])
|
||||
AC_CONFIG_HEADERS([trader-desk/auto-config.h])
|
||||
AC_CONFIG_MACRO_DIRS([m4])
|
||||
|
||||
# Checks for programs.
|
||||
AC_PROG_CC
|
||||
AC_GNU_SOURCE
|
||||
AC_PROG_CXX
|
||||
AC_PROG_AWK
|
||||
AC_PROG_CPP
|
||||
AC_PROG_INSTALL
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_MAKE_SET
|
||||
AC_PROG_LIBTOOL
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION([0.19.3])
|
||||
|
||||
AC_CHECK_PROGS([mysql_config], [mariadb_config mysql_config], [no])
|
||||
if [[ "x$mysql_config" == "xno" ]]; then
|
||||
AC_MSG_FAILURE([cannot find MySQL-type client libraries])
|
||||
fi
|
||||
if [[ "x$mysql_config" == "xmysql_config" ]]; then
|
||||
AC_SUBST([HAVE_MYSQL], [1])
|
||||
else
|
||||
AC_SUBST([HAVE_MYSQL], [0])
|
||||
fi
|
||||
if [[ "x$mysql_config" == "xmariadb_config" ]]; then
|
||||
AC_SUBST([HAVE_MARIADB], [1])
|
||||
else
|
||||
AC_SUBST([HAVE_MARIADB], [0])
|
||||
fi
|
||||
|
||||
# Checks for libraries.
|
||||
PKG_CHECK_MODULES([gtk_config], [gtkmm-3.0 gthread-2.0 dmbcs-market-data-api fmt])
|
||||
gtk_config_CFLAGS="${pkg_cv_gtk_config_CFLAGS} `${mysql_config} --include`"
|
||||
gtk_config_LIBS="${pkg_cv_gtk_config_LIBS} `${mysql_config} --libs`"
|
||||
|
||||
# Checks for header files.
|
||||
AC_CHECK_HEADERS([libintl.h])
|
||||
|
||||
# Checks for typedefs, structures, and compiler characteristics.
|
||||
AC_CHECK_HEADER_STDBOOL
|
||||
AC_C_INLINE
|
||||
AC_TYPE_SIZE_T
|
||||
AC_TYPE_UINT32_T
|
||||
|
||||
# Checks for library functions.
|
||||
AC_FUNC_MKTIME
|
||||
AC_FUNC_STRTOD
|
||||
AC_CHECK_FUNCS([floor localtime_r pow setlocale sqrt])
|
||||
|
||||
# Check for optional database pre-population data.
|
||||
AM_CONDITIONAL([database_data], [test -f data.sql.xz])
|
||||
|
||||
|
||||
AC_CONFIG_FILES([trader-desk.pc
|
||||
makefile
|
||||
po/Makefile.in
|
||||
trader-desk/makefile])
|
||||
dnl doc/makefile
|
||||
|
||||
AC_OUTPUT
|
||||
BIN
data.sql.xz
Normal file
BIN
data.sql.xz
Normal file
Binary file not shown.
18
doc/.gitignore
vendored
Normal file
18
doc/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
trader-desk.aux
|
||||
trader-desk.cp
|
||||
trader-desk.dvi
|
||||
trader-desk.fn
|
||||
trader-desk.ky
|
||||
trader-desk.log
|
||||
trader-desk.pg
|
||||
trader-desk.tmp
|
||||
trader-desk.toc
|
||||
trader-desk.tp
|
||||
trader-desk.vr
|
||||
trader-desk.bbl
|
||||
trader-desk.blg
|
||||
trader-desk.info
|
||||
trader-desk.man
|
||||
trader-desk.html
|
||||
trader-desk.cps
|
||||
trader-desk.pdf
|
||||
BIN
doc/detailed-analysis.png
Normal file
BIN
doc/detailed-analysis.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
BIN
doc/detailed-analysis.xcf
Normal file
BIN
doc/detailed-analysis.xcf
Normal file
Binary file not shown.
505
doc/fdl.texi
Normal file
505
doc/fdl.texi
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
@c The GNU Free Documentation License.
|
||||
@center Version 1.3, 3 November 2008
|
||||
|
||||
@c This file is intended to be included within another document,
|
||||
@c hence no sectioning command or @node.
|
||||
|
||||
@display
|
||||
Copyright @copyright{} 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
|
||||
@uref{http://fsf.org/}
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
@end display
|
||||
|
||||
@enumerate 0
|
||||
@item
|
||||
PREAMBLE
|
||||
|
||||
The purpose of this License is to make a manual, textbook, or other
|
||||
functional and useful document @dfn{free} in the sense of freedom: to
|
||||
assure everyone the effective freedom to copy and redistribute it,
|
||||
with or without modifying it, either commercially or noncommercially.
|
||||
Secondarily, this License preserves for the author and publisher a way
|
||||
to get credit for their work, while not being considered responsible
|
||||
for modifications made by others.
|
||||
|
||||
This License is a kind of ``copyleft'', which means that derivative
|
||||
works of the document must themselves be free in the same sense. It
|
||||
complements the GNU General Public License, which is a copyleft
|
||||
license designed for free software.
|
||||
|
||||
We have designed this License in order to use it for manuals for free
|
||||
software, because free software needs free documentation: a free
|
||||
program should come with manuals providing the same freedoms that the
|
||||
software does. But this License is not limited to software manuals;
|
||||
it can be used for any textual work, regardless of subject matter or
|
||||
whether it is published as a printed book. We recommend this License
|
||||
principally for works whose purpose is instruction or reference.
|
||||
|
||||
@item
|
||||
APPLICABILITY AND DEFINITIONS
|
||||
|
||||
This License applies to any manual or other work, in any medium, that
|
||||
contains a notice placed by the copyright holder saying it can be
|
||||
distributed under the terms of this License. Such a notice grants a
|
||||
world-wide, royalty-free license, unlimited in duration, to use that
|
||||
work under the conditions stated herein. The ``Document'', below,
|
||||
refers to any such manual or work. Any member of the public is a
|
||||
licensee, and is addressed as ``you''. You accept the license if you
|
||||
copy, modify or distribute the work in a way requiring permission
|
||||
under copyright law.
|
||||
|
||||
A ``Modified Version'' of the Document means any work containing the
|
||||
Document or a portion of it, either copied verbatim, or with
|
||||
modifications and/or translated into another language.
|
||||
|
||||
A ``Secondary Section'' is a named appendix or a front-matter section
|
||||
of the Document that deals exclusively with the relationship of the
|
||||
publishers or authors of the Document to the Document's overall
|
||||
subject (or to related matters) and contains nothing that could fall
|
||||
directly within that overall subject. (Thus, if the Document is in
|
||||
part a textbook of mathematics, a Secondary Section may not explain
|
||||
any mathematics.) The relationship could be a matter of historical
|
||||
connection with the subject or with related matters, or of legal,
|
||||
commercial, philosophical, ethical or political position regarding
|
||||
them.
|
||||
|
||||
The ``Invariant Sections'' are certain Secondary Sections whose titles
|
||||
are designated, as being those of Invariant Sections, in the notice
|
||||
that says that the Document is released under this License. If a
|
||||
section does not fit the above definition of Secondary then it is not
|
||||
allowed to be designated as Invariant. The Document may contain zero
|
||||
Invariant Sections. If the Document does not identify any Invariant
|
||||
Sections then there are none.
|
||||
|
||||
The ``Cover Texts'' are certain short passages of text that are listed,
|
||||
as Front-Cover Texts or Back-Cover Texts, in the notice that says that
|
||||
the Document is released under this License. A Front-Cover Text may
|
||||
be at most 5 words, and a Back-Cover Text may be at most 25 words.
|
||||
|
||||
A ``Transparent'' copy of the Document means a machine-readable copy,
|
||||
represented in a format whose specification is available to the
|
||||
general public, that is suitable for revising the document
|
||||
straightforwardly with generic text editors or (for images composed of
|
||||
pixels) generic paint programs or (for drawings) some widely available
|
||||
drawing editor, and that is suitable for input to text formatters or
|
||||
for automatic translation to a variety of formats suitable for input
|
||||
to text formatters. A copy made in an otherwise Transparent file
|
||||
format whose markup, or absence of markup, has been arranged to thwart
|
||||
or discourage subsequent modification by readers is not Transparent.
|
||||
An image format is not Transparent if used for any substantial amount
|
||||
of text. A copy that is not ``Transparent'' is called ``Opaque''.
|
||||
|
||||
Examples of suitable formats for Transparent copies include plain
|
||||
ASCII without markup, Texinfo input format, La@TeX{} input
|
||||
format, SGML or XML using a publicly available
|
||||
DTD, and standard-conforming simple HTML,
|
||||
PostScript or PDF designed for human modification. Examples
|
||||
of transparent image formats include PNG, XCF and
|
||||
JPG@. Opaque formats include proprietary formats that can be
|
||||
read and edited only by proprietary word processors, SGML or
|
||||
XML for which the DTD and/or processing tools are
|
||||
not generally available, and the machine-generated HTML,
|
||||
PostScript or PDF produced by some word processors for
|
||||
output purposes only.
|
||||
|
||||
The ``Title Page'' means, for a printed book, the title page itself,
|
||||
plus such following pages as are needed to hold, legibly, the material
|
||||
this License requires to appear in the title page. For works in
|
||||
formats which do not have any title page as such, ``Title Page'' means
|
||||
the text near the most prominent appearance of the work's title,
|
||||
preceding the beginning of the body of the text.
|
||||
|
||||
The ``publisher'' means any person or entity that distributes copies
|
||||
of the Document to the public.
|
||||
|
||||
A section ``Entitled XYZ'' means a named subunit of the Document whose
|
||||
title either is precisely XYZ or contains XYZ in parentheses following
|
||||
text that translates XYZ in another language. (Here XYZ stands for a
|
||||
specific section name mentioned below, such as ``Acknowledgements'',
|
||||
``Dedications'', ``Endorsements'', or ``History''.) To ``Preserve the Title''
|
||||
of such a section when you modify the Document means that it remains a
|
||||
section ``Entitled XYZ'' according to this definition.
|
||||
|
||||
The Document may include Warranty Disclaimers next to the notice which
|
||||
states that this License applies to the Document. These Warranty
|
||||
Disclaimers are considered to be included by reference in this
|
||||
License, but only as regards disclaiming warranties: any other
|
||||
implication that these Warranty Disclaimers may have is void and has
|
||||
no effect on the meaning of this License.
|
||||
|
||||
@item
|
||||
VERBATIM COPYING
|
||||
|
||||
You may copy and distribute the Document in any medium, either
|
||||
commercially or noncommercially, provided that this License, the
|
||||
copyright notices, and the license notice saying this License applies
|
||||
to the Document are reproduced in all copies, and that you add no other
|
||||
conditions whatsoever to those of this License. You may not use
|
||||
technical measures to obstruct or control the reading or further
|
||||
copying of the copies you make or distribute. However, you may accept
|
||||
compensation in exchange for copies. If you distribute a large enough
|
||||
number of copies you must also follow the conditions in section 3.
|
||||
|
||||
You may also lend copies, under the same conditions stated above, and
|
||||
you may publicly display copies.
|
||||
|
||||
@item
|
||||
COPYING IN QUANTITY
|
||||
|
||||
If you publish printed copies (or copies in media that commonly have
|
||||
printed covers) of the Document, numbering more than 100, and the
|
||||
Document's license notice requires Cover Texts, you must enclose the
|
||||
copies in covers that carry, clearly and legibly, all these Cover
|
||||
Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
|
||||
the back cover. Both covers must also clearly and legibly identify
|
||||
you as the publisher of these copies. The front cover must present
|
||||
the full title with all words of the title equally prominent and
|
||||
visible. You may add other material on the covers in addition.
|
||||
Copying with changes limited to the covers, as long as they preserve
|
||||
the title of the Document and satisfy these conditions, can be treated
|
||||
as verbatim copying in other respects.
|
||||
|
||||
If the required texts for either cover are too voluminous to fit
|
||||
legibly, you should put the first ones listed (as many as fit
|
||||
reasonably) on the actual cover, and continue the rest onto adjacent
|
||||
pages.
|
||||
|
||||
If you publish or distribute Opaque copies of the Document numbering
|
||||
more than 100, you must either include a machine-readable Transparent
|
||||
copy along with each Opaque copy, or state in or with each Opaque copy
|
||||
a computer-network location from which the general network-using
|
||||
public has access to download using public-standard network protocols
|
||||
a complete Transparent copy of the Document, free of added material.
|
||||
If you use the latter option, you must take reasonably prudent steps,
|
||||
when you begin distribution of Opaque copies in quantity, to ensure
|
||||
that this Transparent copy will remain thus accessible at the stated
|
||||
location until at least one year after the last time you distribute an
|
||||
Opaque copy (directly or through your agents or retailers) of that
|
||||
edition to the public.
|
||||
|
||||
It is requested, but not required, that you contact the authors of the
|
||||
Document well before redistributing any large number of copies, to give
|
||||
them a chance to provide you with an updated version of the Document.
|
||||
|
||||
@item
|
||||
MODIFICATIONS
|
||||
|
||||
You may copy and distribute a Modified Version of the Document under
|
||||
the conditions of sections 2 and 3 above, provided that you release
|
||||
the Modified Version under precisely this License, with the Modified
|
||||
Version filling the role of the Document, thus licensing distribution
|
||||
and modification of the Modified Version to whoever possesses a copy
|
||||
of it. In addition, you must do these things in the Modified Version:
|
||||
|
||||
@enumerate A
|
||||
@item
|
||||
Use in the Title Page (and on the covers, if any) a title distinct
|
||||
from that of the Document, and from those of previous versions
|
||||
(which should, if there were any, be listed in the History section
|
||||
of the Document). You may use the same title as a previous version
|
||||
if the original publisher of that version gives permission.
|
||||
|
||||
@item
|
||||
List on the Title Page, as authors, one or more persons or entities
|
||||
responsible for authorship of the modifications in the Modified
|
||||
Version, together with at least five of the principal authors of the
|
||||
Document (all of its principal authors, if it has fewer than five),
|
||||
unless they release you from this requirement.
|
||||
|
||||
@item
|
||||
State on the Title page the name of the publisher of the
|
||||
Modified Version, as the publisher.
|
||||
|
||||
@item
|
||||
Preserve all the copyright notices of the Document.
|
||||
|
||||
@item
|
||||
Add an appropriate copyright notice for your modifications
|
||||
adjacent to the other copyright notices.
|
||||
|
||||
@item
|
||||
Include, immediately after the copyright notices, a license notice
|
||||
giving the public permission to use the Modified Version under the
|
||||
terms of this License, in the form shown in the Addendum below.
|
||||
|
||||
@item
|
||||
Preserve in that license notice the full lists of Invariant Sections
|
||||
and required Cover Texts given in the Document's license notice.
|
||||
|
||||
@item
|
||||
Include an unaltered copy of this License.
|
||||
|
||||
@item
|
||||
Preserve the section Entitled ``History'', Preserve its Title, and add
|
||||
to it an item stating at least the title, year, new authors, and
|
||||
publisher of the Modified Version as given on the Title Page. If
|
||||
there is no section Entitled ``History'' in the Document, create one
|
||||
stating the title, year, authors, and publisher of the Document as
|
||||
given on its Title Page, then add an item describing the Modified
|
||||
Version as stated in the previous sentence.
|
||||
|
||||
@item
|
||||
Preserve the network location, if any, given in the Document for
|
||||
public access to a Transparent copy of the Document, and likewise
|
||||
the network locations given in the Document for previous versions
|
||||
it was based on. These may be placed in the ``History'' section.
|
||||
You may omit a network location for a work that was published at
|
||||
least four years before the Document itself, or if the original
|
||||
publisher of the version it refers to gives permission.
|
||||
|
||||
@item
|
||||
For any section Entitled ``Acknowledgements'' or ``Dedications'', Preserve
|
||||
the Title of the section, and preserve in the section all the
|
||||
substance and tone of each of the contributor acknowledgements and/or
|
||||
dedications given therein.
|
||||
|
||||
@item
|
||||
Preserve all the Invariant Sections of the Document,
|
||||
unaltered in their text and in their titles. Section numbers
|
||||
or the equivalent are not considered part of the section titles.
|
||||
|
||||
@item
|
||||
Delete any section Entitled ``Endorsements''. Such a section
|
||||
may not be included in the Modified Version.
|
||||
|
||||
@item
|
||||
Do not retitle any existing section to be Entitled ``Endorsements'' or
|
||||
to conflict in title with any Invariant Section.
|
||||
|
||||
@item
|
||||
Preserve any Warranty Disclaimers.
|
||||
@end enumerate
|
||||
|
||||
If the Modified Version includes new front-matter sections or
|
||||
appendices that qualify as Secondary Sections and contain no material
|
||||
copied from the Document, you may at your option designate some or all
|
||||
of these sections as invariant. To do this, add their titles to the
|
||||
list of Invariant Sections in the Modified Version's license notice.
|
||||
These titles must be distinct from any other section titles.
|
||||
|
||||
You may add a section Entitled ``Endorsements'', provided it contains
|
||||
nothing but endorsements of your Modified Version by various
|
||||
parties---for example, statements of peer review or that the text has
|
||||
been approved by an organization as the authoritative definition of a
|
||||
standard.
|
||||
|
||||
You may add a passage of up to five words as a Front-Cover Text, and a
|
||||
passage of up to 25 words as a Back-Cover Text, to the end of the list
|
||||
of Cover Texts in the Modified Version. Only one passage of
|
||||
Front-Cover Text and one of Back-Cover Text may be added by (or
|
||||
through arrangements made by) any one entity. If the Document already
|
||||
includes a cover text for the same cover, previously added by you or
|
||||
by arrangement made by the same entity you are acting on behalf of,
|
||||
you may not add another; but you may replace the old one, on explicit
|
||||
permission from the previous publisher that added the old one.
|
||||
|
||||
The author(s) and publisher(s) of the Document do not by this License
|
||||
give permission to use their names for publicity for or to assert or
|
||||
imply endorsement of any Modified Version.
|
||||
|
||||
@item
|
||||
COMBINING DOCUMENTS
|
||||
|
||||
You may combine the Document with other documents released under this
|
||||
License, under the terms defined in section 4 above for modified
|
||||
versions, provided that you include in the combination all of the
|
||||
Invariant Sections of all of the original documents, unmodified, and
|
||||
list them all as Invariant Sections of your combined work in its
|
||||
license notice, and that you preserve all their Warranty Disclaimers.
|
||||
|
||||
The combined work need only contain one copy of this License, and
|
||||
multiple identical Invariant Sections may be replaced with a single
|
||||
copy. If there are multiple Invariant Sections with the same name but
|
||||
different contents, make the title of each such section unique by
|
||||
adding at the end of it, in parentheses, the name of the original
|
||||
author or publisher of that section if known, or else a unique number.
|
||||
Make the same adjustment to the section titles in the list of
|
||||
Invariant Sections in the license notice of the combined work.
|
||||
|
||||
In the combination, you must combine any sections Entitled ``History''
|
||||
in the various original documents, forming one section Entitled
|
||||
``History''; likewise combine any sections Entitled ``Acknowledgements'',
|
||||
and any sections Entitled ``Dedications''. You must delete all
|
||||
sections Entitled ``Endorsements.''
|
||||
|
||||
@item
|
||||
COLLECTIONS OF DOCUMENTS
|
||||
|
||||
You may make a collection consisting of the Document and other documents
|
||||
released under this License, and replace the individual copies of this
|
||||
License in the various documents with a single copy that is included in
|
||||
the collection, provided that you follow the rules of this License for
|
||||
verbatim copying of each of the documents in all other respects.
|
||||
|
||||
You may extract a single document from such a collection, and distribute
|
||||
it individually under this License, provided you insert a copy of this
|
||||
License into the extracted document, and follow this License in all
|
||||
other respects regarding verbatim copying of that document.
|
||||
|
||||
@item
|
||||
AGGREGATION WITH INDEPENDENT WORKS
|
||||
|
||||
A compilation of the Document or its derivatives with other separate
|
||||
and independent documents or works, in or on a volume of a storage or
|
||||
distribution medium, is called an ``aggregate'' if the copyright
|
||||
resulting from the compilation is not used to limit the legal rights
|
||||
of the compilation's users beyond what the individual works permit.
|
||||
When the Document is included in an aggregate, this License does not
|
||||
apply to the other works in the aggregate which are not themselves
|
||||
derivative works of the Document.
|
||||
|
||||
If the Cover Text requirement of section 3 is applicable to these
|
||||
copies of the Document, then if the Document is less than one half of
|
||||
the entire aggregate, the Document's Cover Texts may be placed on
|
||||
covers that bracket the Document within the aggregate, or the
|
||||
electronic equivalent of covers if the Document is in electronic form.
|
||||
Otherwise they must appear on printed covers that bracket the whole
|
||||
aggregate.
|
||||
|
||||
@item
|
||||
TRANSLATION
|
||||
|
||||
Translation is considered a kind of modification, so you may
|
||||
distribute translations of the Document under the terms of section 4.
|
||||
Replacing Invariant Sections with translations requires special
|
||||
permission from their copyright holders, but you may include
|
||||
translations of some or all Invariant Sections in addition to the
|
||||
original versions of these Invariant Sections. You may include a
|
||||
translation of this License, and all the license notices in the
|
||||
Document, and any Warranty Disclaimers, provided that you also include
|
||||
the original English version of this License and the original versions
|
||||
of those notices and disclaimers. In case of a disagreement between
|
||||
the translation and the original version of this License or a notice
|
||||
or disclaimer, the original version will prevail.
|
||||
|
||||
If a section in the Document is Entitled ``Acknowledgements'',
|
||||
``Dedications'', or ``History'', the requirement (section 4) to Preserve
|
||||
its Title (section 1) will typically require changing the actual
|
||||
title.
|
||||
|
||||
@item
|
||||
TERMINATION
|
||||
|
||||
You may not copy, modify, sublicense, or distribute the Document
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense, or distribute it is void, and
|
||||
will automatically terminate your rights under this License.
|
||||
|
||||
However, if you cease all violation of this License, then your license
|
||||
from a particular copyright holder is reinstated (a) provisionally,
|
||||
unless and until the copyright holder explicitly and finally
|
||||
terminates your license, and (b) permanently, if the copyright holder
|
||||
fails to notify you of the violation by some reasonable means prior to
|
||||
60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, receipt of a copy of some or all of the same material does
|
||||
not give you any rights to use it.
|
||||
|
||||
@item
|
||||
FUTURE REVISIONS OF THIS LICENSE
|
||||
|
||||
The Free Software Foundation may publish new, revised versions
|
||||
of the GNU Free Documentation License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns. See
|
||||
@uref{http://www.gnu.org/copyleft/}.
|
||||
|
||||
Each version of the License is given a distinguishing version number.
|
||||
If the Document specifies that a particular numbered version of this
|
||||
License ``or any later version'' applies to it, you have the option of
|
||||
following the terms and conditions either of that specified version or
|
||||
of any later version that has been published (not as a draft) by the
|
||||
Free Software Foundation. If the Document does not specify a version
|
||||
number of this License, you may choose any version ever published (not
|
||||
as a draft) by the Free Software Foundation. If the Document
|
||||
specifies that a proxy can decide which future versions of this
|
||||
License can be used, that proxy's public statement of acceptance of a
|
||||
version permanently authorizes you to choose that version for the
|
||||
Document.
|
||||
|
||||
@item
|
||||
RELICENSING
|
||||
|
||||
``Massive Multiauthor Collaboration Site'' (or ``MMC Site'') means any
|
||||
World Wide Web server that publishes copyrightable works and also
|
||||
provides prominent facilities for anybody to edit those works. A
|
||||
public wiki that anybody can edit is an example of such a server. A
|
||||
``Massive Multiauthor Collaboration'' (or ``MMC'') contained in the
|
||||
site means any set of copyrightable works thus published on the MMC
|
||||
site.
|
||||
|
||||
``CC-BY-SA'' means the Creative Commons Attribution-Share Alike 3.0
|
||||
license published by Creative Commons Corporation, a not-for-profit
|
||||
corporation with a principal place of business in San Francisco,
|
||||
California, as well as future copyleft versions of that license
|
||||
published by that same organization.
|
||||
|
||||
``Incorporate'' means to publish or republish a Document, in whole or
|
||||
in part, as part of another Document.
|
||||
|
||||
An MMC is ``eligible for relicensing'' if it is licensed under this
|
||||
License, and if all works that were first published under this License
|
||||
somewhere other than this MMC, and subsequently incorporated in whole
|
||||
or in part into the MMC, (1) had no cover texts or invariant sections,
|
||||
and (2) were thus incorporated prior to November 1, 2008.
|
||||
|
||||
The operator of an MMC Site may republish an MMC contained in the site
|
||||
under CC-BY-SA on the same site at any time before August 1, 2009,
|
||||
provided the MMC is eligible for relicensing.
|
||||
|
||||
@end enumerate
|
||||
|
||||
@page
|
||||
@heading ADDENDUM: How to use this License for your documents
|
||||
|
||||
To use this License in a document you have written, include a copy of
|
||||
the License in the document and put the following copyright and
|
||||
license notices just after the title page:
|
||||
|
||||
@smallexample
|
||||
@group
|
||||
Copyright (C) @var{year} @var{your name}.
|
||||
Permission is granted to copy, distribute and/or modify this document
|
||||
under the terms of the GNU Free Documentation License, Version 1.3
|
||||
or any later version published by the Free Software Foundation;
|
||||
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
|
||||
Texts. A copy of the license is included in the section entitled ``GNU
|
||||
Free Documentation License''.
|
||||
@end group
|
||||
@end smallexample
|
||||
|
||||
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts,
|
||||
replace the ``with@dots{}Texts.''@: line with this:
|
||||
|
||||
@smallexample
|
||||
@group
|
||||
with the Invariant Sections being @var{list their titles}, with
|
||||
the Front-Cover Texts being @var{list}, and with the Back-Cover Texts
|
||||
being @var{list}.
|
||||
@end group
|
||||
@end smallexample
|
||||
|
||||
If you have Invariant Sections without Cover Texts, or some other
|
||||
combination of the three, merge those two alternatives to suit the
|
||||
situation.
|
||||
|
||||
If your document contains nontrivial examples of program code, we
|
||||
recommend releasing these examples in parallel under your choice of
|
||||
free software license, such as the GNU General Public License,
|
||||
to permit their use in free software.
|
||||
|
||||
@c Local Variables:
|
||||
@c ispell-local-pdict: "ispell-dict"
|
||||
@c End:
|
||||
BIN
doc/grid.png
Normal file
BIN
doc/grid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
BIN
doc/grid.xcf
Normal file
BIN
doc/grid.xcf
Normal file
Binary file not shown.
24
doc/makefile.am
Normal file
24
doc/makefile.am
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
man1_MANS = trader-desk.man
|
||||
|
||||
info_INFO = trader-desk.info
|
||||
|
||||
pkgdata_DATA = trader-desk.pdf trader-desk.html
|
||||
|
||||
trader-desk : trader-desk.texinfo trader-desk.css
|
||||
makeinfo --html --css-include=trader-desk.css $<
|
||||
cp {detailed-analysis,grid,preferences}.png trader-desk
|
||||
|
||||
trader-desk.html : trader-desk.texinfo trader-desk.css
|
||||
makeinfo --html --no-split --no-headers --css-include=trader-desk.css $<
|
||||
|
||||
trader-desk.pdf : trader-desk.texinfo
|
||||
makeinfo --pdf --no-headers $<
|
||||
|
||||
trader-desk.info : trader-desk.texinfo
|
||||
makeinfo $<
|
||||
|
||||
CLEANFILES = trader-desk.html trader-desk.info trader-desk.pdf \
|
||||
trader-desk.aux trader-desk.bbl trader-desk.blg trader-desk.cp \
|
||||
trader-desk.cps trader-desk.dvi trader-desk.fn \
|
||||
trader-desk.ky trader-desk.log trader-desk.pg trader-desk.tmp \
|
||||
trader-desk.toc trader-desk.tp trader-desk.vr
|
||||
BIN
doc/preferences.png
Normal file
BIN
doc/preferences.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
BIN
doc/preferences.xcf
Normal file
BIN
doc/preferences.xcf
Normal file
Binary file not shown.
5
doc/trader-desk.css
Normal file
5
doc/trader-desk.css
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
body { max-width: 800px;
|
||||
font-family: times;
|
||||
text-align: justify;
|
||||
margin: 0 auto;
|
||||
padding: 0 0.5em; }
|
||||
1025
doc/trader-desk.texinfo
Normal file
1025
doc/trader-desk.texinfo
Normal file
File diff suppressed because it is too large
Load diff
58
makefile.am
Normal file
58
makefile.am
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Copyright (c) 2020 Dale Mellor
|
||||
#
|
||||
# This file is part of the trader-desk package.
|
||||
#
|
||||
# The trader-desk package is free software: you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# The trader-desk package is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
|
||||
SUBDIRS = trader-desk . po
|
||||
|
||||
ACLOCAL_AMFLAGS = --install -I m4
|
||||
|
||||
if database_data
|
||||
DTA = data.sql.xz
|
||||
else
|
||||
DTA =
|
||||
endif
|
||||
dist_pkgdata_DATA = trader-desk.png ${DTA}
|
||||
|
||||
pkgconfigdir = ${libdir}/pkgconfig
|
||||
pkgconfig_DATA = trader-desk.pc
|
||||
|
||||
DISTFILES = ABOUT-NLS
|
||||
|
||||
MAINTAINERCLEANFILES = ABOUT-NLS \
|
||||
aclocal.m4 \
|
||||
build-aux/compile \
|
||||
build-aux/config.guess \
|
||||
build-aux/config.rpath \
|
||||
build-aux/config.sub \
|
||||
build-aux/depcomp \
|
||||
build-aux/install-sh \
|
||||
build-aux/ltmain.sh \
|
||||
build-aux/missing \
|
||||
config.status \
|
||||
config.log \
|
||||
configure \
|
||||
makefile \
|
||||
makefile.in \
|
||||
po/en@boldquot.po \
|
||||
po/en@quot.po \
|
||||
po/Makefile.in.in \
|
||||
po/Makevars.template \
|
||||
po/trader-desk.pot
|
||||
|
||||
# Don't know why we need to do this.
|
||||
dist-hook:
|
||||
cp -rp aclocal.m4 AUTHORS build-aux ChangeLog configure configure.ac COPYING INSTALL libtool makefile.am makefile.in NEWS README trader-desk.png $(top_distdir)
|
||||
19
po/.gitignore
vendored
Normal file
19
po/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
*.gmo
|
||||
ChangeLog
|
||||
en@*
|
||||
Makefile
|
||||
Makefile.in
|
||||
Makefile.in.in
|
||||
Makevars.template
|
||||
messages.mo
|
||||
POTFILES
|
||||
Rules-quot
|
||||
boldquot.sed
|
||||
en@boldquot.header
|
||||
en@quot.header
|
||||
insert-header.sin
|
||||
quot.sed
|
||||
remove-potcdate.sed
|
||||
remove-potcdate.sin
|
||||
stamp-po
|
||||
trader-desk.pot
|
||||
2
po/LINGUAS
Normal file
2
po/LINGUAS
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
en@quot
|
||||
en@boldquot
|
||||
98
po/Makevars
Normal file
98
po/Makevars
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Copyright (c) 2017 Dale Mellor
|
||||
#
|
||||
#
|
||||
# This file is part of the trader-desk package.
|
||||
#
|
||||
# The trader-desk package is free software: you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# The trader-desk package is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
|
||||
# Makefile variables for PO directory in any package using GNU gettext.
|
||||
|
||||
|
||||
# Usually the message domain is the same as the package name.
|
||||
DOMAIN = $(PACKAGE)
|
||||
|
||||
# These two variables depend on the location of this directory.
|
||||
subdir = po
|
||||
top_builddir = ..
|
||||
|
||||
# These options get passed to xgettext.
|
||||
XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
|
||||
|
||||
# This is the copyright holder that gets inserted into the header of the
|
||||
# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
|
||||
# package. (Note that the msgstr strings, extracted from the package's
|
||||
# sources, belong to the copyright holder of the package.) Translators are
|
||||
# expected to transfer the copyright for their translations to this person
|
||||
# or entity, or to disclaim their copyright. The empty string stands for
|
||||
# the public domain; in this case the translators are expected to disclaim
|
||||
# their copyright.
|
||||
COPYRIGHT_HOLDER = Dale Mellor
|
||||
|
||||
# This tells whether or not to prepend "GNU " prefix to the package
|
||||
# name that gets inserted into the header of the $(DOMAIN).pot file.
|
||||
# Possible values are "yes", "no", or empty. If it is empty, try to
|
||||
# detect it automatically by scanning the files in $(top_srcdir) for
|
||||
# "GNU packagename" string.
|
||||
PACKAGE_GNU = no
|
||||
|
||||
# This is the email address or URL to which the translators shall report
|
||||
# bugs in the untranslated strings:
|
||||
# - Strings which are not entire sentences, see the maintainer guidelines
|
||||
# in the GNU gettext documentation, section 'Preparing Strings'.
|
||||
# - Strings which use unclear terms or require additional context to be
|
||||
# understood.
|
||||
# - Strings which make invalid assumptions about notation of date, time or
|
||||
# money.
|
||||
# - Pluralisation problems.
|
||||
# - Incorrect English spelling.
|
||||
# - Incorrect formatting.
|
||||
# It can be your email address, or a mailing list address where translators
|
||||
# can write to without being subscribed, or the URL of a web page through
|
||||
# which the translators can contact you.
|
||||
MSGID_BUGS_ADDRESS =
|
||||
|
||||
# This is the list of locale categories, beyond LC_MESSAGES, for which the
|
||||
# message catalogs shall be used. It is usually empty.
|
||||
EXTRA_LOCALE_CATEGORIES =
|
||||
|
||||
# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
|
||||
# context. Possible values are "yes" and "no". Set this to yes if the
|
||||
# package uses functions taking also a message context, like pgettext(), or
|
||||
# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
|
||||
USE_MSGCTXT = no
|
||||
|
||||
# These options get passed to msgmerge.
|
||||
# Useful options are in particular:
|
||||
# --previous to keep previous msgids of translated messages,
|
||||
# --quiet to reduce the verbosity.
|
||||
MSGMERGE_OPTIONS =
|
||||
|
||||
# These options get passed to msginit.
|
||||
# If you want to disable line wrapping when writing PO files, add
|
||||
# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
|
||||
# MSGINIT_OPTIONS.
|
||||
MSGINIT_OPTIONS =
|
||||
|
||||
# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
|
||||
# has changed. Possible values are "yes" and "no". Set this to no if
|
||||
# the POT file is checked in the repository and the version control
|
||||
# program ignores timestamps.
|
||||
PO_DEPENDS_ON_POT = yes
|
||||
|
||||
# This tells whether or not to forcibly update $(DOMAIN).pot and
|
||||
# regenerate PO files on "make dist". Possible values are "yes" and
|
||||
# "no". Set this to no if the POT file and PO files are maintained
|
||||
# externally.
|
||||
DIST_DEPENDS_ON_UPDATE_PO = yes
|
||||
27
po/POTFILES.in
Normal file
27
po/POTFILES.in
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright (c) 2020 Dale Mellor
|
||||
#
|
||||
# This file is part of the trader-desk package.
|
||||
#
|
||||
# The trader-desk package is free software: you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# The trader-desk package is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
|
||||
# List of source files which contain translatable strings.
|
||||
|
||||
trader-desk/chart.cc
|
||||
trader-desk/company-name-entry.cc
|
||||
trader-desk/date-range-scale.cc
|
||||
trader-desk/moving-average-analyzer.h
|
||||
trader-desk/sd-envelope-analyzer.h
|
||||
trader-desk/shares-scale.cc
|
||||
trader-desk/trader-desk.cc
|
||||
179
po/en_GB.po
Normal file
179
po/en_GB.po
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# English translations for trader-desk package.
|
||||
# Copyright (C) 2020 Dale Mellor
|
||||
# This file is distributed under the same license as the trader-desk package.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: trader-desk 0.1\n"
|
||||
"POT-Creation-Date: 2017-02-10 12:31+0000\n"
|
||||
"PO-Revision-Date: 2017-02-10 12:35+0000\n"
|
||||
"Last-Translator: Dale Mellor\n"
|
||||
"Language-Team: English (British)\n"
|
||||
"Language: en_GB\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: trader-desk/chart.cc:257
|
||||
msgctxt "Label"
|
||||
msgid "Position value (pounds)"
|
||||
msgstr "Position value (£)"
|
||||
|
||||
#: trader-desk/company-name-entry.cc:44
|
||||
msgctxt "Label"
|
||||
msgid "Company name: "
|
||||
msgstr "Company name: "
|
||||
|
||||
#: trader-desk/date-range-scale.cc:9
|
||||
#, c-format
|
||||
msgctxt "Label"
|
||||
msgid "Date range = %.0f days"
|
||||
msgstr "Date range = %.0f days"
|
||||
|
||||
#: trader-desk/moving-average-analyzer.h:29
|
||||
#, c-format
|
||||
msgctxt "Label"
|
||||
msgid "Mean window = %.0f days"
|
||||
msgstr "Mean window = %.0f days"
|
||||
|
||||
#: trader-desk/positions-widget.cc:14
|
||||
msgctxt "Label"
|
||||
msgid "Positions"
|
||||
msgstr "Positions"
|
||||
|
||||
#: trader-desk/positions-widget.cc:24
|
||||
msgctxt "Label"
|
||||
msgid "None"
|
||||
msgstr "None"
|
||||
|
||||
#: trader-desk/sd-envelope-analyzer.h:32
|
||||
#, c-format
|
||||
msgctxt "Label Abbrev:Standard deviation"
|
||||
msgid "Envelope width = %.2f x std. dev."
|
||||
msgstr "Envelope width = %.2f x std. dev."
|
||||
|
||||
#: trader-desk/shares-scale.cc:12
|
||||
#, c-format
|
||||
msgctxt "Label"
|
||||
msgid "Number of shares = %.0f"
|
||||
msgstr "Number of shares = %.0f"
|
||||
|
||||
#: trader-desk/trader-desk.cc:27
|
||||
msgctxt "Window title"
|
||||
msgid "Updating database"
|
||||
msgstr "Updating database"
|
||||
|
||||
#: trader-desk/trader-desk.cc:36
|
||||
msgctxt "Information"
|
||||
msgid "trader-desk: Fetching data from Internet"
|
||||
msgstr "trader-desk: Fetching data from Internet"
|
||||
|
||||
#: trader-desk/trader-desk.cc:55
|
||||
msgctxt "Label"
|
||||
msgid "Fixed cost of trading"
|
||||
msgstr "Fixed cost of trading"
|
||||
|
||||
#: trader-desk/trader-desk.cc:59
|
||||
msgctxt "Units:Worded:Monetary"
|
||||
msgid "pounds"
|
||||
msgstr "pounds"
|
||||
|
||||
#: trader-desk/trader-desk.cc:62
|
||||
msgctxt "Label"
|
||||
msgid "Proportional cost of trading"
|
||||
msgstr "Proportional cost of trading"
|
||||
|
||||
#: trader-desk/trader-desk.cc:65
|
||||
msgctxt "Units:Worded"
|
||||
msgid "percent"
|
||||
msgstr "percent"
|
||||
|
||||
#: trader-desk/trader-desk.cc:76
|
||||
msgctxt "Window title"
|
||||
msgid "Trader-Desk: Preferences"
|
||||
msgstr "Trader-Desk: Preferences"
|
||||
|
||||
#: trader-desk/trader-desk.cc:155 trader-desk/trader-desk.cc:167
|
||||
msgctxt "Instruction"
|
||||
msgid "Select market"
|
||||
msgstr "Select market"
|
||||
|
||||
#: trader-desk/trader-desk.cc:175
|
||||
msgctxt "Join-A"
|
||||
msgid "Sorry, there are no"
|
||||
msgstr "Sorry, there are no"
|
||||
|
||||
#: trader-desk/trader-desk.cc:179
|
||||
msgctxt "Join-A"
|
||||
msgid "new markets."
|
||||
msgstr "new markets."
|
||||
|
||||
#: trader-desk/trader-desk.cc:182 trader-desk/trader-desk.cc:232
|
||||
msgctxt "Instruction"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: trader-desk/trader-desk.cc:208
|
||||
msgctxt "Label"
|
||||
msgid "Market"
|
||||
msgstr "Market"
|
||||
|
||||
#: trader-desk/trader-desk.cc:224
|
||||
msgctxt "Instruction, Join-B"
|
||||
msgid "Double-click a market name"
|
||||
msgstr "Double-click a market name"
|
||||
|
||||
#: trader-desk/trader-desk.cc:229
|
||||
msgctxt "Instruction, Join-B"
|
||||
msgid "to ingest its data"
|
||||
msgstr "to ingest its data"
|
||||
|
||||
#: trader-desk/trader-desk.cc:350 trader-desk/trader-desk.cc:386
|
||||
msgid "No Internet Connection"
|
||||
msgstr "No Internet Connection"
|
||||
|
||||
#: trader-desk/trader-desk.cc:446
|
||||
msgctxt "Menu"
|
||||
msgid "_File"
|
||||
msgstr "_File"
|
||||
|
||||
#: trader-desk/trader-desk.cc:448
|
||||
msgctxt "Menu"
|
||||
msgid "_Preferences"
|
||||
msgstr "_Preferences"
|
||||
|
||||
#: trader-desk/trader-desk.cc:453
|
||||
msgctxt "Menu"
|
||||
msgid "_Display"
|
||||
msgstr "_Display"
|
||||
|
||||
#: trader-desk/trader-desk.cc:455
|
||||
msgctxt "Menu"
|
||||
msgid "_Market"
|
||||
msgstr "_Market"
|
||||
|
||||
#: trader-desk/trader-desk.cc:457
|
||||
msgctxt "Menu"
|
||||
msgid "_Help"
|
||||
msgstr "_Help"
|
||||
|
||||
#: trader-desk/trader-desk.cc:458
|
||||
msgctxt "Menu"
|
||||
msgid "_About"
|
||||
msgstr "_About"
|
||||
|
||||
#: trader-desk/trader-desk.cc:501
|
||||
msgctxt "Menu"
|
||||
msgid "Update _latest data"
|
||||
msgstr "Update _latest data"
|
||||
|
||||
#: trader-desk/trader-desk.cc:506
|
||||
msgctxt "Menu"
|
||||
msgid "Update _close data"
|
||||
msgstr "Update _close data"
|
||||
|
||||
#: trader-desk/trader-desk.cc:511
|
||||
msgctxt "Menu"
|
||||
msgid "_Ingest new market"
|
||||
msgstr "_Ingest new market"
|
||||
113
setup-hint.sh
Normal file
113
setup-hint.sh
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Get an up to date system with all the packages we require.
|
||||
|
||||
sudo apt update
|
||||
sudo apt upgrade -y
|
||||
sudo apt install -y git build-essential libmariadbclient-dev \
|
||||
mariadb-server autoconf autopoint \
|
||||
libssl-dev libtool libgtkmm-3.0-dev \
|
||||
libcurl4-openssl-dev cmake texinfo xauth \
|
||||
gettext ed
|
||||
|
||||
|
||||
# Set up the database to make it possible for anybody (in particular the
|
||||
# trader-desk application) to use the database root account.
|
||||
|
||||
sudo mysql mysql <<-\EOF
|
||||
update mysql.user set password='', plugin='';
|
||||
flush privileges;
|
||||
exit
|
||||
EOF
|
||||
|
||||
|
||||
# Download, build and install gcc 9.3. We use the latest C++20 standards,
|
||||
# and need the best compiler we can get.
|
||||
|
||||
sudo bash -c 'cat > /etc/ld.so.conf.d/01-local.conf' <<-\EOF
|
||||
/usr/local/lib64
|
||||
/usr/local/lib
|
||||
EOF
|
||||
sudo ldconfig
|
||||
|
||||
mkdir ${HOME}/sources; cd ${HOME}/sources
|
||||
wget ftp://ftp.gnu.org/gnu/gcc/gcc-9.3.0/gcc-9.3.0.tar.xz
|
||||
wget ftp://ftp.gnu.org/gnu/gmp/gmp-6.2.0.tar.xz
|
||||
wget ftp://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz
|
||||
wget ftp://ftp.gnu.org/gnu/mpfr/mpfr-4.0.2.tar.xz
|
||||
tar xf gcc-9.3.0.tar.xz
|
||||
cd gcc-9.3.0
|
||||
tar xf ../gmp-6.2.0.tar.xz
|
||||
ln -s gmp-6.2.0 gmp
|
||||
tar xf ../mpc-1.1.0.tar.gz
|
||||
ln -s mpc-1.1.0 mpc
|
||||
tar xf ../mpfr-4.0.2.tar.xz
|
||||
ln -s mpfr-4.0.2 mpfr
|
||||
./configure --enable-languages=c,c++ --disable-bootstrap --disable-multilib
|
||||
make -j2 # Takes a long time.
|
||||
sudo make install
|
||||
cd ${HOME}/sources; rm -rf gcc-9.3.0
|
||||
|
||||
|
||||
# Make a couple of third-party packages that don’t come with the Debian
|
||||
# system.
|
||||
|
||||
cmake_build() { cd $1
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DBUILD_SHARED_LIBS=TRUE ..
|
||||
make -j2
|
||||
sudo make install
|
||||
}
|
||||
|
||||
|
||||
cd ${HOME}/sources
|
||||
git clone https://github.com/fmtlib/fmt.git
|
||||
( cmake_build fmt )
|
||||
|
||||
|
||||
cd ${HOME}/sources
|
||||
git clone https://github.com/jpbarrette/curlpp.git
|
||||
( cmake_build curlpp )
|
||||
|
||||
sudo ed /usr/local/lib/pkgconfig/curlpp.pc <<-\EOF
|
||||
1,$s@-Llib@-L${prefix}/lib@
|
||||
w
|
||||
q
|
||||
EOF
|
||||
|
||||
|
||||
# Now build the DMBCS packages which make up the trader-desk application.
|
||||
|
||||
autotools_build() { cd $1
|
||||
autoreconf --install
|
||||
./configure
|
||||
make -j2
|
||||
sudo make install
|
||||
}
|
||||
|
||||
cd ${HOME}
|
||||
|
||||
git clone https://rdmp.org/dmbcs/market-data-api.git dmbcs-market-data-api
|
||||
( autotools_build dmbcs-market-data-api )
|
||||
|
||||
git clone https://rdmp.org/dmbcs/trader-desk.git dmbcs-trader-desk
|
||||
( autotools_build dmbcs-trader-desk )
|
||||
|
||||
sudo ldconfig
|
||||
|
||||
mkdir ${HOME}/.config
|
||||
|
||||
|
||||
# Take heed of this message!
|
||||
|
||||
cat <<EOF
|
||||
|
||||
The dmbcs-trader-desk installation is complete. If you have just done
|
||||
this on a fresh Debian installation, you will now need to log out and
|
||||
then back in again with the -X option on the ssh line, so that
|
||||
graphics will appear in front of you.
|
||||
|
||||
Then just type trader-desk.
|
||||
|
||||
EOF
|
||||
13
trader-desk.pc.in
Normal file
13
trader-desk.pc.in
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
prefix = @prefix@
|
||||
exec_prefix = ${prefix}
|
||||
libdir = ${exec_prefix}/lib
|
||||
includedir = ${prefix}/include
|
||||
|
||||
Name : trader-desk
|
||||
Description : DMBCS Trader Desk
|
||||
Version : @VERSION@
|
||||
Requires : gtkmm-3.0 gthread-2.0 dmbcs-market-data-api
|
||||
Libs : -L${libdir} @gtk_config_LIBS@ \
|
||||
-ldmbcs-market-data-api -ltrader-desk
|
||||
Cflags : -I${includedir} @gtk_config_CFLAGS@ \
|
||||
-DHAVE_MYSQL=@HAVE_MYSQL@ -DHAVE_MARIADB=@HAVE_MARIADB@
|
||||
BIN
trader-desk.png
Normal file
BIN
trader-desk.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 B |
89
trader-desk/alpha-vantage--monitor.cc
Normal file
89
trader-desk/alpha-vantage--monitor.cc
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#include <trader-desk/alpha-vantage--monitor.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
|
||||
unique_ptr<Alpha_Vantage__Monitor::Clocks> Alpha_Vantage__Monitor::clocks;
|
||||
|
||||
|
||||
|
||||
void Alpha_Vantage__Monitor::make_clock_widgets (Preferences& P,
|
||||
Gtk::HBox& C)
|
||||
{
|
||||
initialize_clocks (P);
|
||||
Clocks& c {*clocks};
|
||||
|
||||
C.pack_start (c.starter);
|
||||
C.pack_start (c.count_down);
|
||||
C.pack_start (c.spacer);
|
||||
C.pack_start (c.strikes_);
|
||||
C.pack_start (c.finisher);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Alpha_Vantage__Monitor::remove_clock_widgets (Gtk::Container& C)
|
||||
{
|
||||
Clocks& c {*clocks};
|
||||
C.remove (c.starter);
|
||||
C.remove (c.count_down);
|
||||
C.remove (c.spacer);
|
||||
C.remove (c.strikes_);
|
||||
C.remove (c.finisher);
|
||||
clocks.reset ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
using system_clock = chrono::system_clock;
|
||||
|
||||
void Alpha_Vantage__Monitor::Clocks::hit ()
|
||||
{
|
||||
try
|
||||
{
|
||||
db.quick() << "insert ignore into alphavantage_ticks (time) value ("
|
||||
<< (system_clock::to_time_t (system_clock::now ()))
|
||||
<< ")";
|
||||
}
|
||||
catch (Mysql::DB_Connection::Exception&)
|
||||
{
|
||||
db.reconnect (db.current_preferences);
|
||||
}
|
||||
|
||||
last_time = chrono::steady_clock::now ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int seconds_since (const chrono::steady_clock::time_point& T)
|
||||
{ return chrono::duration_cast<chrono::seconds>
|
||||
(chrono::steady_clock::now () - T)
|
||||
.count (); }
|
||||
|
||||
using C = Alpha_Vantage__Monitor::Clocks;
|
||||
|
||||
const C& C::update ()
|
||||
{
|
||||
const time_t now {system_clock::to_time_t (system_clock::now ())};
|
||||
|
||||
try
|
||||
{
|
||||
auto I {db.instruction ()};
|
||||
I << "delete from alphavantage_ticks where time<"
|
||||
<< (now - 24 * 60 * 60);
|
||||
I.execute ();
|
||||
strikes
|
||||
= db.scalar_result<int> (0, "select count(*) from alphavantage_ticks");
|
||||
strikes_.set_text (to_string (strikes));
|
||||
count_down.set_text (to_string (max (0,
|
||||
12 - seconds_since (last_time))));
|
||||
}
|
||||
catch (Mysql::DB_Connection::Exception&) {}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
90
trader-desk/alpha-vantage--monitor.h
Normal file
90
trader-desk/alpha-vantage--monitor.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#ifndef DMBCS__TRADER_DESK__ALPHA_VANTAGE__MONITOR__H
|
||||
#define DMBCS__TRADER_DESK__ALPHA_VANTAGE__MONITOR__H
|
||||
|
||||
|
||||
#include <trader-desk/alpha-vantage.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
struct Alpha_Vantage__Monitor
|
||||
{
|
||||
using Error = Alpha_Vantage::Error;
|
||||
using Throttled = Alpha_Vantage::Throttled;
|
||||
using Bad_API_Key = Alpha_Vantage::Bad_API_Key;
|
||||
using TO_DO = Alpha_Vantage::TO_DO;
|
||||
|
||||
struct Clocks
|
||||
{
|
||||
DB db;
|
||||
chrono::steady_clock::time_point last_time;
|
||||
int strikes {0};
|
||||
|
||||
Gtk::Label starter {"AlphaVantage charge: "};
|
||||
Gtk::Label count_down {"0"};
|
||||
Gtk::Label spacer {" "};
|
||||
Gtk::Label strikes_ {"0"};
|
||||
Gtk::Label finisher {"/500"};
|
||||
|
||||
|
||||
explicit Clocks (Preferences& P) : db {P}
|
||||
{ update (); }
|
||||
|
||||
void hit ();
|
||||
const Clocks& update ();
|
||||
};
|
||||
|
||||
|
||||
|
||||
static unique_ptr<Clocks> clocks;
|
||||
|
||||
|
||||
|
||||
static void initialize_clocks (Preferences& P)
|
||||
{
|
||||
if (! Alpha_Vantage__Monitor::clocks)
|
||||
Alpha_Vantage__Monitor::clocks = make_unique<Clocks> (P);
|
||||
}
|
||||
|
||||
|
||||
static void make_clock_widgets (Preferences&, Gtk::HBox& container);
|
||||
|
||||
static void remove_clock_widgets (Gtk::Container&);
|
||||
|
||||
|
||||
static TO_DO get_closing_prices /* override */
|
||||
(const Update_Closing_Prices::Company& C,
|
||||
const string& market_component_extension,
|
||||
Preferences& P,
|
||||
const function <void (const Update_Closing_Prices::Data&)> injector)
|
||||
{
|
||||
initialize_clocks (P);
|
||||
const TO_DO ret {Alpha_Vantage::get_closing_prices
|
||||
(C, market_component_extension, P, injector)};
|
||||
if (ret == TO_DO::FINISHED) clocks->hit ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Update_Latest_Prices::Data get_latest_data /* override */
|
||||
(const Update_Latest_Prices::Company& C,
|
||||
const string& market_component_extension,
|
||||
Preferences& P)
|
||||
{
|
||||
initialize_clocks (P);
|
||||
auto ret {Alpha_Vantage::get_latest_data
|
||||
(C, market_component_extension, P)};
|
||||
clocks->hit ();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} ; /* End of class Alpha_Vantage__Monitor. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__ALPHA_VANTAGE__MONITOR__H. */
|
||||
251
trader-desk/alpha-vantage.cc
Normal file
251
trader-desk/alpha-vantage.cc
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/alpha-vantage.h>
|
||||
#include <trader-desk/time-series.h> /* For time utilities. */
|
||||
#include <curlpp/Options.hpp>
|
||||
#include <curlpp/Easy.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <regex>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
static bool throttle ()
|
||||
{
|
||||
namespace C = chrono;
|
||||
using clock = C::system_clock;
|
||||
|
||||
static clock::time_point LAST_CALL;
|
||||
|
||||
const C::duration wait {clock::now () - LAST_CALL};
|
||||
if (wait < C::seconds {12})
|
||||
{
|
||||
usleep (min (number<C::microseconds> (C::seconds {12} - wait),
|
||||
number<C::microseconds> (C::milliseconds {500})));
|
||||
return 1;
|
||||
}
|
||||
|
||||
LAST_CALL = clock::now ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void throw_error (const string& query, const string& json)
|
||||
{
|
||||
static const regex re {"\"(Note|Error Message)\": +\"([^\"]*)"};
|
||||
smatch match;
|
||||
if (! regex_search (json, match, re))
|
||||
throw Alpha_Vantage::Error
|
||||
{"AlphaVantage returned garbled error message"};
|
||||
if (match [1] == "Note") throw Alpha_Vantage::Throttled {match [2]};
|
||||
static const regex re2 {"apikey.*(invalid|missing)", regex::icase};
|
||||
if (regex_search (match [2].str (), re2))
|
||||
throw Alpha_Vantage::Bad_API_Key {match [2]};
|
||||
throw Alpha_Vantage::Error
|
||||
{match [2].str ()
|
||||
+ "\n\n[The query was ‘" + query + "’.]"};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string get_curl_response (const string& query)
|
||||
{
|
||||
namespace Curl = cURLpp;
|
||||
namespace Opt = Curl::Options;
|
||||
|
||||
string response;
|
||||
Curl::Easy request;
|
||||
|
||||
request.setOpt (Opt::Url {query});
|
||||
|
||||
request.setOpt (Opt::WriteFunction
|
||||
{[&response] (char* buffer, size_t size, size_t n)
|
||||
{ response += string {buffer,
|
||||
buffer + size * n};
|
||||
return size * n; }});
|
||||
|
||||
request.setOpt (Opt::SslVerifyPeer {0});
|
||||
|
||||
request.perform ();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static time_t to_time_t (const Update_Closing_Prices::Data& D)
|
||||
{ return t (D.year, D.month, D.day); }
|
||||
|
||||
|
||||
inline string maybe_dot (const string& X)
|
||||
{ return X.length () ? '.' + X : X; }
|
||||
|
||||
|
||||
|
||||
static string get_timeseries_csv
|
||||
(const Update_Closing_Prices::Company& company,
|
||||
string market_component_extension,
|
||||
const string& api_key,
|
||||
const bool over_100)
|
||||
{
|
||||
const string query
|
||||
{fmt::format ("https://www.alphavantage.co/query"
|
||||
"?function=TIME_SERIES_DAILY_ADJUSTED"
|
||||
"&symbol={}"
|
||||
"&apikey={}"
|
||||
"&datatype=csv"
|
||||
"&outputsize={}",
|
||||
company.symbol + maybe_dot (market_component_extension),
|
||||
api_key,
|
||||
over_100 ? "full" : "compact") };
|
||||
|
||||
const string ret {get_curl_response (query)};
|
||||
|
||||
if (ret [0] == '{') throw_error (query, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static vector<string> fields (const string& csv, const char separator)
|
||||
{
|
||||
vector<string> ret;
|
||||
for (size_t cursor {0}; ; )
|
||||
{
|
||||
const size_t next_cursor {csv.find (separator, cursor)};
|
||||
ret.push_back (csv.substr (cursor, next_cursor - cursor));
|
||||
if (next_cursor == csv.npos) return ret;
|
||||
cursor = next_cursor + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static istream& operator>> (istream& I, Update_Closing_Prices::Data& D)
|
||||
{
|
||||
static char comma;
|
||||
static double dividend_amount, split_coefficient;
|
||||
|
||||
I >> D.year >> comma >> D.month >> comma >> D.day >> comma
|
||||
>> D.open >> comma >> D.high >> comma
|
||||
>> D.low >> comma >> D.close >> comma
|
||||
>> D.adj_close >> comma >> D.volume >> comma
|
||||
>> dividend_amount >> comma >> split_coefficient;
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
static vector<Update_Closing_Prices::Data> parse_csv
|
||||
(const string& results, const int company_seqid)
|
||||
{
|
||||
istringstream O {results.substr (results.find ('\n'))};
|
||||
vector<Update_Closing_Prices::Data> data;
|
||||
|
||||
for (;;) { Update_Closing_Prices::Data datum;
|
||||
datum.company_seqid = company_seqid;
|
||||
O >> datum;
|
||||
if (! O.good ()) return data;
|
||||
data.push_back (move (datum)); }
|
||||
}
|
||||
|
||||
static int days_ago (const time_t T)
|
||||
{
|
||||
using Clock = chrono::system_clock;
|
||||
return number<chrono::hours> (Clock::now ()
|
||||
- Clock::from_time_t (T))
|
||||
/ 24;
|
||||
}
|
||||
|
||||
auto Alpha_Vantage::get_closing_prices
|
||||
(const Update_Closing_Prices::Company& company,
|
||||
const string& market_component_extension,
|
||||
const Preferences& P,
|
||||
const function <void (const Update_Closing_Prices::Data&)> injector)
|
||||
-> TO_DO
|
||||
{
|
||||
if (throttle ()) return MORE_WORK;
|
||||
|
||||
vector<Update_Closing_Prices::Data> data
|
||||
{parse_csv (get_timeseries_csv
|
||||
(company,
|
||||
market_component_extension,
|
||||
P.market_data_service_key,
|
||||
days_ago (company.last_close_date) > 100),
|
||||
company.seqid)};
|
||||
|
||||
auto i { find_if (data.rbegin (), data.rend (),
|
||||
[L = company.last_close_date]
|
||||
(const Update_Closing_Prices::Data& D)
|
||||
{ return to_time_t (D) > L; })};
|
||||
|
||||
if (i != data.rbegin()) --i;
|
||||
|
||||
for (auto j {i}; j != data.rend (); ++j)
|
||||
injector (*j);
|
||||
|
||||
return FINISHED;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string get_snap_csv (const Update_Latest_Prices::Company& company,
|
||||
const string& market_component_extension,
|
||||
const string& api_key)
|
||||
{
|
||||
const auto query
|
||||
{ fmt::format ("https://www.alphavantage.co/query"
|
||||
"?function=GLOBAL_QUOTE"
|
||||
"&symbol={}"
|
||||
"&datatype=csv"
|
||||
"&apikey={}",
|
||||
company.symbol + maybe_dot (market_component_extension),
|
||||
api_key) };
|
||||
|
||||
const string ret {get_curl_response (query)};
|
||||
if (ret [0] == '{') throw_error (query, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline double extract_price_csv (const string& X)
|
||||
{
|
||||
return strtod (fields (fields (X, '\n') [1], ',') [4].data (),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Update_Latest_Prices::Data Alpha_Vantage::get_latest_data
|
||||
(const Update_Latest_Prices::Company& company,
|
||||
const string& market_component_extension,
|
||||
const Preferences& P)
|
||||
{
|
||||
while (throttle ()) ;
|
||||
|
||||
return
|
||||
{ .company_seqid = company.seqid,
|
||||
.time = chrono::system_clock::now (),
|
||||
.price = extract_price_csv
|
||||
(get_snap_csv (company,
|
||||
market_component_extension,
|
||||
P.market_data_service_key)) };
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
68
trader-desk/alpha-vantage.h
Normal file
68
trader-desk/alpha-vantage.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__ALPHA_VANTAGE__H
|
||||
#define DMBCS__TRADER_DESK__ALPHA_VANTAGE__H
|
||||
|
||||
|
||||
#include <trader-desk/update-closing-prices.h>
|
||||
#include <trader-desk/update-latest-prices.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/* This is a struct rather than a namespace because we want to be able
|
||||
* to derive from it a version with a proper API key, and want to
|
||||
* alias it as a type in ‘update_*_prices.cc’.
|
||||
*
|
||||
* The reason for this seemingly unnecessary abstraction is that in
|
||||
* the past we use Yahoo! Finance for this purpose, but they pulled
|
||||
* their API. We now want to keep things flexible so that other data
|
||||
* sources might be used in future. */
|
||||
|
||||
struct Alpha_Vantage
|
||||
{
|
||||
struct Error : runtime_error { using runtime_error::runtime_error; };
|
||||
struct Throttled : Error { using Error::Error; };
|
||||
struct Bad_API_Key : Error { using Error::Error; };
|
||||
|
||||
|
||||
enum TO_DO : bool {FINISHED = 0, MORE_WORK = 1};
|
||||
static TO_DO get_closing_prices /* override */
|
||||
(const Update_Closing_Prices::Company&,
|
||||
const string& market_component_extension,
|
||||
const Preferences& P,
|
||||
const function <void (const Update_Closing_Prices::Data&)> injector);
|
||||
|
||||
|
||||
static Update_Latest_Prices::Data get_latest_data /* override */
|
||||
(const Update_Latest_Prices::Company&,
|
||||
const string& market_component_extension,
|
||||
const Preferences&);
|
||||
|
||||
|
||||
} ; /* End of class Alpha_Vantage. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__ALPHA_VANTAGE__H. */
|
||||
48
trader-desk/analyzer.cc
Normal file
48
trader-desk/analyzer.cc
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/delta-analyzer.h>
|
||||
#include <trader-desk/sd-envelope-analyzer.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Analyzer_Stack constructor, a factory which
|
||||
* knows about all the detailed analyzers. If a new analyzer is to be
|
||||
* added to the system, this file will need modifying to accomodate it;
|
||||
* it is the *only* place that needs to know anything about specific
|
||||
* analyzers. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Analyzer_Stack::Analyzer_Stack (Chart_Data& chart_data, Preferences&)
|
||||
{
|
||||
analyzers.emplace_back (new SD_Envelope_Analyzer {chart_data});
|
||||
analyzers.emplace_back (new Delta_Analyzer {chart_data});
|
||||
|
||||
for (auto &a : analyzers)
|
||||
a -> signal_redraw_needed ()
|
||||
. connect ([this] { redraw_needed_signal.emit (); });
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
195
trader-desk/analyzer.h
Normal file
195
trader-desk/analyzer.h
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__ANALYZER__H
|
||||
#define DMBCS__TRADER_DESK__ANALYZER__H
|
||||
|
||||
|
||||
#include <trader-desk/chart-context.h>
|
||||
#include <trader-desk/chart-data.h>
|
||||
#include <trader-desk/tide-mark.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Analyzer pure interface, and of the \c
|
||||
* Analyzer_Stack class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Abstract idea of an object which can draw anything it wants onto a
|
||||
* chart, hopefully to provide some useful illumination of the data to
|
||||
* the user. Additionally a control widget can be given which will
|
||||
* appear at the right-hand side of the screen. */
|
||||
|
||||
struct Analyzer
|
||||
{
|
||||
/** Obligatory null destructor for a purely virtual base class. */
|
||||
virtual ~Analyzer () = default;
|
||||
|
||||
/** The returned object, if not empty, will be stacked up at the right
|
||||
* edge of the window. This is optional functionality for analyzers,
|
||||
* so we provide a default implementation which adds no controls. */
|
||||
virtual vector <Gtk::Widget *> make_control_widgets ()
|
||||
{ return {}; }
|
||||
|
||||
|
||||
/** Draw whatever into the \a chart_context. The tide_marks are the
|
||||
* points in time at which tide-lines need adding, if they are to
|
||||
* show on the \c hand_analysis_widget. */
|
||||
virtual void graph_draw_hook
|
||||
(Chart_Context &canvas,
|
||||
Tide_Mark::List ¬es,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &) = 0;
|
||||
|
||||
/** An opportunity for the analyzer to increase the area in which
|
||||
* time-series are plotted, if this is necessary to display all of
|
||||
* the auxiliary data, for example. Most analyzers donʼt need this,
|
||||
* so we provide a null default. */
|
||||
virtual void stretch_outline (Time_Series::Range &) {}
|
||||
|
||||
/** Emitted internally to signal to the world that analytical results
|
||||
* have changed (and probably need re-rendering on-screen). */
|
||||
virtual sigc::signal<void> &signal_redraw_needed () = 0;
|
||||
|
||||
/** Allow an analyzer to respond to mouse presses on the chart canvas.
|
||||
*
|
||||
* As it stands, the system only allows for one of the analyzers to
|
||||
* respond to this, and that privilege is currently taken by the \c
|
||||
* Delta_Analyzer. */
|
||||
virtual bool button_down (int const /*x*/, int const /*y*/) { return 0; }
|
||||
|
||||
/** Allow an analyzer to respond to mouse button releases. */
|
||||
virtual bool button_up (int const /*x*/, int const /*y*/) { return 0; }
|
||||
|
||||
/** Allow an analyzer to respond to mouse drags on the chart canvas. */
|
||||
virtual bool button_move (int const /*x*/, int const /*y*/) { return 0; }
|
||||
|
||||
|
||||
}; /* End of class Analyzer. */
|
||||
|
||||
|
||||
|
||||
/** Whether there is one analyzer in the system or twenty, this class
|
||||
* makes it appear to the rest of the application as if there were just
|
||||
* one, thus simplifying matters. It is simply a wrapper around a
|
||||
* container of analyzers. This also removes any dependencies of the
|
||||
* application on the specifics of the analyzers keeping the
|
||||
* abstraction purely abstract: the \c Analyzer_Stack constructor is a
|
||||
* shrink-wrapped analyzer factory. */
|
||||
|
||||
struct Analyzer_Stack : Analyzer
|
||||
{
|
||||
/** The individual analyzers that we provide a home for. */
|
||||
vector <unique_ptr <Analyzer>> analyzers;
|
||||
|
||||
/** If any analyzer indicates the need for a graphics re-draw, we will
|
||||
* fire this signal which the application needs to listen out for. */
|
||||
sigc::signal<void> redraw_needed_signal;
|
||||
|
||||
/** If not \c nullptr, this analyzer is working with the mouse. */
|
||||
Analyzer *mouse_user {nullptr};
|
||||
|
||||
|
||||
/** The class constructor is actually a comprehensive analyzer
|
||||
* factory, and returns a fully populated object ready to run the
|
||||
* full gamut of analysis at the userʼs behest. */
|
||||
explicit Analyzer_Stack (Chart_Data &, Preferences&);
|
||||
|
||||
|
||||
/** Provide a container-load of widgets which the various analyzers
|
||||
* need to function. These are not at all life-time managed and the
|
||||
* application must take responsibility to destroy the objects when
|
||||
* necessary. The application is expected to allocate space
|
||||
* horizontally at the right-hand edge of the data charts. */
|
||||
vector <Gtk::Widget *> make_control_widgets () override
|
||||
{
|
||||
vector <Gtk::Widget*> ret;
|
||||
for (auto &a : analyzers)
|
||||
{
|
||||
auto const w = a->make_control_widgets ();
|
||||
ret.insert (end (ret), begin (w), end (w));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** Give every analyzer an opportunity to draw onto the chart
|
||||
* canvas. */
|
||||
void graph_draw_hook
|
||||
(Chart_Context &c,
|
||||
Tide_Mark::List &t,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &p) override
|
||||
{
|
||||
for (auto &a : analyzers)
|
||||
a->graph_draw_hook (c, t, number_shares, p);
|
||||
}
|
||||
|
||||
|
||||
/** Give every analyzer a go at stretching the size of the chart
|
||||
* canvas. */
|
||||
void stretch_outline (Time_Series::Range &r) override
|
||||
{ for (auto &a : analyzers) a->stretch_outline (r); }
|
||||
|
||||
|
||||
/** Return our re-draw signal. */
|
||||
sigc::signal<void> &signal_redraw_needed () override
|
||||
{ return redraw_needed_signal; }
|
||||
|
||||
|
||||
/** Give each analyzer notification of a button press event until one
|
||||
* responds and acts on the event. */
|
||||
bool button_down (int const x, int const y) override
|
||||
{
|
||||
for (auto &a : analyzers)
|
||||
if (a->button_down (x, y))
|
||||
{
|
||||
mouse_user = a.get ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/** Give the ‘active’ analyzer information about a mouse button-up
|
||||
* event. */
|
||||
bool button_up (int const x, int const y) override
|
||||
{ return mouse_user && mouse_user->button_up (x, y); }
|
||||
|
||||
|
||||
/** Give the ‘active’ analyzer information about a mouse move
|
||||
* event. */
|
||||
bool button_move (int const x, int const y) override
|
||||
{ return mouse_user && mouse_user->button_move (x, y); }
|
||||
|
||||
|
||||
}; /* End of class Analyzer_Stack. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__ANALYZER__H. */
|
||||
205
trader-desk/application--ingest-market.cc
Normal file
205
trader-desk/application--ingest-market.cc
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/application.h>
|
||||
#include <dmbcs-market-data-api.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Application::ingest_new_market
|
||||
* mega-method. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
void Application::ingest_new_market ()
|
||||
{
|
||||
try
|
||||
{
|
||||
DB db {user_prefs};
|
||||
Markets markets {db};
|
||||
|
||||
try {
|
||||
update_market_meta_data (markets, db);
|
||||
}
|
||||
catch (Market_Data_Api::Bad_Communication const &e)
|
||||
{
|
||||
Gtk::MessageDialog {*window, e.what (), 0, Gtk::MESSAGE_WARNING}
|
||||
.run ();
|
||||
return;
|
||||
}
|
||||
|
||||
Gtk::Dialog dialog (pgettext ("Instruction", "Select market"),
|
||||
*window,
|
||||
Gtk::DIALOG_MODAL);
|
||||
|
||||
auto add_label = [&dialog] (string const &m)
|
||||
{
|
||||
Gtk::Label *const t = new Gtk::Label;
|
||||
t->set_markup (m);
|
||||
dialog . get_vbox () -> pack_start (*Gtk::manage (t),
|
||||
Gtk::PACK_SHRINK);
|
||||
};
|
||||
|
||||
add_label (pgettext ("Instruction", "Select market"));
|
||||
|
||||
if (find_if (begin (markets), end (markets),
|
||||
[] (Markets::value_type const &m)
|
||||
{ return ! m.second.tracked; })
|
||||
== markets.end ())
|
||||
{
|
||||
add_label (string {"<span color=\"red\">"}
|
||||
+ pgettext ("Join-A", "Sorry, there are no")
|
||||
+ "</span>");
|
||||
|
||||
add_label (string {"<span color=\"red\">"}
|
||||
+ pgettext ("Join-A", "new markets.")
|
||||
+ "</span>");
|
||||
|
||||
dialog . add_button (pgettext ("Instruction", "Cancel"),
|
||||
Gtk::RESPONSE_CANCEL);
|
||||
dialog . set_size_request (300, 250);
|
||||
dialog . show_all ();
|
||||
|
||||
dialog . run ();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Gtk::TreeModelColumn<string> name;
|
||||
Gtk::TreeModelColumn<Market_Meta_Data const *> data;
|
||||
Gtk::TreeModel::ColumnRecord columns;
|
||||
columns.add (name);
|
||||
columns.add (data);
|
||||
|
||||
auto list = Gtk::ListStore::create (columns);
|
||||
for (auto const &u : markets)
|
||||
if (! u.second.tracked)
|
||||
{
|
||||
auto b = list->append ();
|
||||
(*b) [name] = u.second.world_data.name;
|
||||
(*b) [data] = &u.second;
|
||||
}
|
||||
|
||||
Gtk::TreeView view {list};
|
||||
view . append_column (pgettext ("Label", "Market"), name);
|
||||
|
||||
Gtk::TreeModel::Path selected_row;
|
||||
|
||||
|
||||
view . signal_row_activated ()
|
||||
. connect ([&selected_row, &dialog]
|
||||
(Gtk::TreeModel::Path const &path,
|
||||
Gtk::TreeViewColumn *const)
|
||||
{ selected_row = path;
|
||||
dialog.response (Gtk::RESPONSE_OK); });
|
||||
|
||||
dialog . get_vbox () -> pack_start (view);
|
||||
|
||||
add_label (string {"<span color=\"red\" size=\"small\">"}
|
||||
+ pgettext ("Instruction, Join-B",
|
||||
"Double-click a market name")
|
||||
+ "</span>");
|
||||
|
||||
add_label (string {"<span color=\"red\" size=\"small\">"}
|
||||
+ pgettext ("Instruction, Join-B",
|
||||
"to ingest its data")
|
||||
+ "</span>");
|
||||
|
||||
dialog . add_button (pgettext ("Instruction", "Cancel"),
|
||||
Gtk::RESPONSE_CANCEL);
|
||||
dialog . set_size_request (300, 250);
|
||||
dialog . show_all ();
|
||||
|
||||
if (Gtk::RESPONSE_OK == dialog . run ())
|
||||
{
|
||||
dialog.hide ();
|
||||
|
||||
auto market_data = *(*list->get_iter (selected_row)) [data];
|
||||
|
||||
start_tracking (markets, db, market_data.world_data.symbol);
|
||||
|
||||
/* Check that the seqid is not in market_grids. */
|
||||
|
||||
auto a = find_if (begin (market_grids),
|
||||
end (market_grids),
|
||||
[seqid = market_data.seqid]
|
||||
(unique_ptr<Chart_Grid> const &g)
|
||||
{ return g->market.seqid == seqid; });
|
||||
|
||||
if (a == end (market_grids))
|
||||
{
|
||||
/* !!!! How is this getting destroyed? */
|
||||
auto *const g {new Chart_Grid {user_prefs, market_data}};
|
||||
|
||||
if (market_grids.empty ()) notebook.remove_page (1);
|
||||
|
||||
market_grids.emplace_back (g);
|
||||
|
||||
g->selection_signal
|
||||
.connect ([this, g]
|
||||
{ hand_analysis->subsume_selected (*g);
|
||||
close_data_menu->set_sensitive (0);
|
||||
last_data_menu->set_sensitive (1);
|
||||
notebook.set_current_page (0); });
|
||||
|
||||
notebook.append_page (*g);
|
||||
display_grid (market_grids.size () - 1);
|
||||
notebook.show_all ();
|
||||
|
||||
/* Now fix up the menus. */
|
||||
{
|
||||
auto const n = market_grids.size () - 1;
|
||||
ostringstream a; a << "display-grid-" << n;
|
||||
|
||||
actions->add (Gtk::Action::create
|
||||
(a.str (), market_data.world_data.name),
|
||||
[this, n] { display_grid (n); });
|
||||
|
||||
ui_manager->add_ui_from_string
|
||||
("<ui>"
|
||||
" <menubar name=\"menu\">"
|
||||
" <menu action=\"display-menu\">"
|
||||
" <menuitem action=\"" + a.str () + "\"/>"
|
||||
" </menu>"
|
||||
" </menubar>"
|
||||
"</ui>");
|
||||
}
|
||||
|
||||
/* !!!! Maybe think of re-setting the connection to
|
||||
* the database here. */
|
||||
|
||||
update_closing_prices ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catch (Market_Data_Api::No_Network &e)
|
||||
{
|
||||
Gtk::MessageDialog {*window, e.what (), 0, Gtk::MESSAGE_WARNING}
|
||||
.run ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
232
trader-desk/application--update-closing-prices.cc
Normal file
232
trader-desk/application--update-closing-prices.cc
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include "alpha-vantage--monitor.h"
|
||||
#include "application.h"
|
||||
#include "update-closing-prices.h"
|
||||
#include <dmbcs-market-data-api.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Application::update_closing_prices
|
||||
* mega-method. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
struct Progress_Dialog : Gtk::Dialog
|
||||
{
|
||||
Gtk::ProgressBar progress_bar;
|
||||
Gtk::Label company_name;
|
||||
|
||||
explicit Progress_Dialog (Gtk::Window &parent);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Progress_Dialog::Progress_Dialog (Gtk::Window &parent)
|
||||
: Gtk::Dialog (pgettext ("Window title", "Updating database"), parent, 1)
|
||||
{
|
||||
company_name.set_ellipsize (Pango::ELLIPSIZE_END);
|
||||
company_name.set_alignment (Gtk::ALIGN_START);
|
||||
get_vbox ()->pack_start
|
||||
(*Gtk::make_managed<Gtk::Label>
|
||||
(pgettext ("Information",
|
||||
"trader-desk: Fetching data from Internet")));
|
||||
get_vbox ()->pack_start (company_name, Gtk::PACK_EXPAND_WIDGET);
|
||||
get_vbox ()->pack_start (progress_bar, Gtk::PACK_SHRINK);
|
||||
add_button (Gtk::Stock::STOP, 0);
|
||||
get_vbox ()->set_spacing (10);
|
||||
set_default_size (400, 10);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" int queue_draw (Gtk::Widget *const w)
|
||||
{ w->queue_draw (); return 0; }
|
||||
|
||||
|
||||
|
||||
struct no_connection_args
|
||||
{ Gtk::Window* window; string message; };
|
||||
|
||||
extern "C" int run_no_internet_connection_message
|
||||
(const no_connection_args *const A)
|
||||
{
|
||||
Gtk::MessageDialog {*A->window, A->message, 0, Gtk::MESSAGE_WARNING}
|
||||
.run ();
|
||||
delete A;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" int delete_widget (Gtk::Widget *const w)
|
||||
{ delete w; return 0; }
|
||||
|
||||
|
||||
|
||||
struct pulse_progress_bar_args
|
||||
{ Progress_Dialog* dialog; double fraction; string company_name; };
|
||||
|
||||
extern "C" int pulse_progress_bar (const pulse_progress_bar_args *const A)
|
||||
{
|
||||
A -> dialog -> progress_bar . set_fraction (A->fraction);
|
||||
A -> dialog -> company_name . set_text (A->company_name);
|
||||
delete A;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct preferences_error_args { Application* app; string message; };
|
||||
|
||||
extern "C" int preferences_error_ (const preferences_error_args *const A)
|
||||
{
|
||||
A->app->preferences_error (A->message);
|
||||
delete A;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void grid_injector (Chart_Grid& grid,
|
||||
Chart** current_chart,
|
||||
const Update_Closing_Prices::Data& data)
|
||||
{
|
||||
if (! *current_chart)
|
||||
*current_chart = grid.find_chart (data.company_seqid);
|
||||
|
||||
if (*current_chart)
|
||||
(*current_chart) -> data
|
||||
. new_event
|
||||
({chrono::system_clock::from_time_t
|
||||
(t (data.year, data.month, data.day))
|
||||
+ (*current_chart)->data.prices.market_close_time,
|
||||
data.close},
|
||||
Chart_Data::NO_SIGNAL);
|
||||
}
|
||||
|
||||
void Application::update_closing_prices ()
|
||||
{
|
||||
const size_t a {(size_t) notebook.get_current_page ()};
|
||||
|
||||
if (a < 1 || a > market_grids.size ()) return;
|
||||
|
||||
Chart_Grid& grid {*market_grids [a - 1]};
|
||||
|
||||
try {
|
||||
DB db {user_prefs};
|
||||
grid.regenerate (db, user_prefs);
|
||||
}
|
||||
catch (const Market_Data_Api::Bad_Communication& e)
|
||||
{
|
||||
Gtk::MessageDialog {*window, e.what (), 0, Gtk::MESSAGE_WARNING}
|
||||
.run ();
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& c : grid.chart) c->data.unaccurate = 1;
|
||||
|
||||
grid.queue_draw ();
|
||||
|
||||
std::thread
|
||||
{[this, progress_dialog = new Progress_Dialog (*window), &grid]
|
||||
{
|
||||
sigc::connection call_id;
|
||||
|
||||
try
|
||||
{
|
||||
DB db {user_prefs};
|
||||
Update_Closing_Prices::Work update {.market = grid.market};
|
||||
|
||||
call_id = progress_dialog->signal_response ()
|
||||
.connect ([&update] (int)
|
||||
{ update.stop = true; });
|
||||
|
||||
/** We donʼt want to hunt for the correct chart every time a
|
||||
* new datum is reported to us, so only do the search when
|
||||
* this is \c nullptr and re-use the last search result
|
||||
* (stored here) otherwise. */
|
||||
Chart* current_chart {nullptr};
|
||||
|
||||
do_update
|
||||
(update,
|
||||
db,
|
||||
user_prefs,
|
||||
|
||||
/* Progress callback. */
|
||||
[progress_dialog, &grid]
|
||||
(const double& x,
|
||||
const Update_Closing_Prices::Company& company)
|
||||
{ gdk_threads_add_idle
|
||||
((int(*)(void*)) pulse_progress_bar,
|
||||
new pulse_progress_bar_args
|
||||
{progress_dialog, x, company.name}); },
|
||||
|
||||
/* Datum injector. */
|
||||
[&db, &grid, ¤t_chart]
|
||||
(Update_Closing_Prices::Data const &data)
|
||||
{ if (data.close == 0) return;
|
||||
sql_injector (db, data);
|
||||
grid_injector (grid, ¤t_chart, data); },
|
||||
|
||||
/* Company_done. */
|
||||
[&grid, ¤t_chart] (int const &company_seqid)
|
||||
{ if (! current_chart)
|
||||
current_chart = grid.find_chart (company_seqid);
|
||||
|
||||
current_chart->data.extremes
|
||||
= current_chart->data
|
||||
.prices
|
||||
.get_range (chrono::hours (50*24));
|
||||
|
||||
current_chart->data.unaccurate = 0;
|
||||
gdk_threads_add_idle ((int(*)(void*))queue_draw,
|
||||
current_chart);
|
||||
current_chart = nullptr; });
|
||||
}
|
||||
|
||||
catch (const Price_Server::Bad_API_Key& E)
|
||||
{
|
||||
gdk_threads_add_idle
|
||||
((int(*)(void*)) preferences_error_,
|
||||
new preferences_error_args {this, E.what ()});
|
||||
}
|
||||
|
||||
catch (const Update_Closing_Prices::No_Connection& E)
|
||||
{
|
||||
gdk_threads_add_idle
|
||||
((int(*)(void*)) run_no_internet_connection_message,
|
||||
new no_connection_args {window, E.what ()});
|
||||
}
|
||||
|
||||
call_id.disconnect ();
|
||||
gdk_threads_add_idle ((int(*)(void*)) delete_widget,
|
||||
progress_dialog);
|
||||
}}
|
||||
.detach ();
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
42
trader-desk/application.cc
Normal file
42
trader-desk/application.cc
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/application.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
void Application::display_grid (size_t const &g)
|
||||
{
|
||||
if (notebook.get_current_page () == 0)
|
||||
{
|
||||
hand_analysis->chart.data.update_extremes (Chart_Grid::DEFAULT_SPAN);
|
||||
hand_analysis->chart.data.return_subsumed ();
|
||||
}
|
||||
|
||||
notebook.set_current_page (g + 1);
|
||||
|
||||
close_data_menu->set_sensitive (1);
|
||||
last_data_menu->set_sensitive (0);
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
119
trader-desk/application.h
Normal file
119
trader-desk/application.h
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__APPLICATION__H
|
||||
#define DMBCS__TRADER_DESK__APPLICATION__H
|
||||
|
||||
|
||||
#include <trader-desk/hand-analysis-widget.h>
|
||||
#include <trader-desk/update-latest-prices.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Application class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** The glue that binds everything together, really half the
|
||||
* implementation of the top-level \c Window defined in
|
||||
* trader-desk.cc.
|
||||
*
|
||||
* [This is still a work in progress. Ultimately we want this \c
|
||||
* Application class to be independent of the (GTK) GUI front-end, so
|
||||
* need to change the split in functionality between this class and the
|
||||
* \c Window class accordingly.] */
|
||||
|
||||
struct Application
|
||||
{
|
||||
/* The following are established outside of this class, and it is
|
||||
* expected that this be done as soon as the class is constructed. */
|
||||
|
||||
/** *The* set of user preferences. May change at infrequent but
|
||||
* random times. */
|
||||
Preferences user_prefs;
|
||||
|
||||
/** The most top-level window of this application. */
|
||||
Gtk::Window *window {nullptr};
|
||||
|
||||
/** A function we can push to Gtk::Idle when we need to recourse to
|
||||
* the preferences dialog due to a problem in the preferences. */
|
||||
function<void (const string&)> preferences_error;
|
||||
|
||||
|
||||
|
||||
/** The part of the menu which is specific to the markets whose data
|
||||
* we have loaded. */
|
||||
Glib::RefPtr <Gtk::Action> last_data_menu;
|
||||
Glib::RefPtr <Gtk::Action> close_data_menu;
|
||||
|
||||
/** All of the actions available to the menu. */
|
||||
Glib::RefPtr <Gtk::ActionGroup> actions;
|
||||
|
||||
/** The menu manager. */
|
||||
Glib::RefPtr <Gtk::UIManager> ui_manager;
|
||||
|
||||
/** The notebook which appears, without tabs, as the main widget in
|
||||
* the \c window. */
|
||||
Gtk::Notebook notebook;
|
||||
|
||||
/** All of the data, organized into a list of grids of charts. */
|
||||
vector<unique_ptr<Chart_Grid>> market_grids;
|
||||
|
||||
/** The widget which shows detailed analysis of a particular companyʼs
|
||||
* data, and allows for much interaction with the user. */
|
||||
unique_ptr<Hand_Analysis_Widget> hand_analysis;
|
||||
|
||||
|
||||
|
||||
/** The application is not in a good state until the constructor has
|
||||
* run, user_prefs have been updated with fixed-up values (by virtue
|
||||
* of the wizard), and create_database() is run.
|
||||
*
|
||||
* !!!! This is far from ideal. */
|
||||
explicit Application (Preferences&& P) : user_prefs {move (P)}
|
||||
{}
|
||||
|
||||
|
||||
/** This method is used to *change* the grid being displayed,
|
||||
* according to their \a position amongst the notebook pages,
|
||||
* i.e. counting from one upwards. */
|
||||
void display_grid (size_t const &position);
|
||||
|
||||
|
||||
/** Mega-method which does all the work (including operating the
|
||||
* display machinery) to get a new market working in the system. */
|
||||
void ingest_new_market ();
|
||||
|
||||
|
||||
/** Mega-method which does all the work to bring a marketʼs data up to
|
||||
* date. */
|
||||
void update_closing_prices ();
|
||||
|
||||
|
||||
} ; /* End of class Application. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__APPLICATION__H. */
|
||||
130
trader-desk/chart-context.cc
Normal file
130
trader-desk/chart-context.cc
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/chart-context.h>
|
||||
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
|
||||
double Chart_Context::x (Time_Point const &t) const
|
||||
{
|
||||
return left_border
|
||||
+ (width - left_border - right_border)
|
||||
* (t - outline.start_time).count ()
|
||||
/ (double) ((outline.end_time - outline.start_time).count ());
|
||||
}
|
||||
|
||||
|
||||
Time_Point Chart_Context::date (int const &x) const
|
||||
{
|
||||
return outline.start_time
|
||||
+ chrono::duration_cast<chrono::seconds>
|
||||
((x - left_border)
|
||||
* (outline.end_time - outline.start_time)
|
||||
/ (double) (width - left_border - right_border));
|
||||
}
|
||||
|
||||
|
||||
double Chart_Context::y (Currency_Value const &value) const
|
||||
{
|
||||
return top_border + (height - bottom_border - top_border)
|
||||
* (1.0 - (value - outline.min_value)
|
||||
/ (outline.max_value - outline.min_value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Currency_Value Chart_Context::value (int const &y) const
|
||||
{
|
||||
return outline.max_value
|
||||
- (outline.max_value - outline.min_value)
|
||||
* (y - top_border)
|
||||
/ (double) (height - top_border - bottom_border);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Context::draw_time_series (Time_Series const &series,
|
||||
Colour const &colour,
|
||||
double const &alpha) const
|
||||
{
|
||||
if (series.size () < 2)
|
||||
return;
|
||||
|
||||
set_source_rgb (colour, alpha);
|
||||
|
||||
auto i = begin (series);
|
||||
|
||||
move_to (*i++);
|
||||
|
||||
while (i != end (series) && i->time >= outline.start_time)
|
||||
line_to (*i++);
|
||||
|
||||
cairo->stroke ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Context::add (Text &text,
|
||||
string const &message,
|
||||
Colour const &colour,
|
||||
Text::Place const &position)
|
||||
{
|
||||
pango->set_text (message);
|
||||
|
||||
Pango::Rectangle const r {pango->get_pixel_logical_extents ()};
|
||||
|
||||
text += Text::Item {message,
|
||||
colour,
|
||||
position,
|
||||
{(double) r.get_width (), (double) r.get_height ()}};
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Context::render (Text &text)
|
||||
{
|
||||
for (auto const &t : text.arrange ({left_border, width - right_border,
|
||||
top_border, height - bottom_border}))
|
||||
{
|
||||
cairo->rectangle (t.placement.x, t.placement.y,
|
||||
t.size.x, t.size.y);
|
||||
|
||||
set_source_rgb (Colour::MARK_LABEL_BACK, 0.5);
|
||||
|
||||
cairo->fill ();
|
||||
|
||||
pango->set_text (t.text);
|
||||
|
||||
cairo->move_to (t.placement.x, t.placement.y);
|
||||
|
||||
pango->add_to_cairo_context (cairo);
|
||||
|
||||
set_source_rgb (t.colour, 0.7);
|
||||
|
||||
cairo->fill ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
162
trader-desk/chart-context.h
Normal file
162
trader-desk/chart-context.h
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__CHART_CONTEXT__H
|
||||
#define DMBCS__TRADER_DESK__CHART_CONTEXT__H
|
||||
|
||||
|
||||
#include <trader-desk/text.h>
|
||||
#include <trader-desk/time-series.h>
|
||||
#include <pangomm.h>
|
||||
#include <cairomm/cairomm.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Chart_Context class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A wrapper around a Cairo context: a canvas on which to draw things.
|
||||
* Methods are provided for drawing lines and filling areas represented
|
||||
* relative to real times and prices, and for laying down and rendering
|
||||
* small items of text.
|
||||
*
|
||||
* The class is really just an implementation detail of \c Chart
|
||||
* (though references are passed out and used in several other
|
||||
* classes), and is instantiated only transiently in that class; there
|
||||
* is not a proper constructor but provision for the \c Chart class to
|
||||
* get an uninitialized object and for that class to then perform full
|
||||
* initialization. */
|
||||
|
||||
struct Chart_Context
|
||||
{
|
||||
/** The `canvas' on which the chart is painted. */
|
||||
Cairo::RefPtr <Cairo::Context> cairo;
|
||||
|
||||
/** An object which helps with painting text onto the above canvas. */
|
||||
Glib::RefPtr <Pango::Layout> pango;
|
||||
|
||||
/** The number of pixels between the left edge of the window and the
|
||||
* side of the chart. */
|
||||
double left_border;
|
||||
|
||||
/** The number of pixels between the right-hand side of the chart and
|
||||
* the window edge. */
|
||||
double right_border;
|
||||
|
||||
/** The number of pixels above the chart. */
|
||||
double top_border;
|
||||
|
||||
/** The number of pixels below the chart. */
|
||||
double bottom_border;
|
||||
|
||||
/** The width of the chart, in pixels. */
|
||||
double width;
|
||||
|
||||
/** The height of the chart, in pixels. */
|
||||
double height;
|
||||
|
||||
/** The (date, price) extremes of the chart. */
|
||||
Time_Series::Range outline;
|
||||
|
||||
/** The collection of text strings which will be placed on the chart
|
||||
* when it is rendered. */
|
||||
Text text;
|
||||
|
||||
|
||||
/** The application has a single (transient) instance of this class,
|
||||
* created and initialized (constructed) within Chart::on_expose. We
|
||||
* provide that method with the means to make an uninitialized
|
||||
* object, and then prohibit any copying or moving of that object. */
|
||||
Chart_Context () = default;
|
||||
|
||||
Chart_Context (Chart_Context const &) = delete;
|
||||
Chart_Context &operator= (Chart_Context const &) = delete;
|
||||
Chart_Context (Chart_Context &&) = delete;
|
||||
Chart_Context &operator= (Chart_Context &&) = delete;
|
||||
|
||||
|
||||
|
||||
/** Plot the chart. */
|
||||
void draw_time_series (Time_Series const &series,
|
||||
Colour const &colour,
|
||||
double const &alpha) const;
|
||||
|
||||
|
||||
/** Move the `pen' to the position on the chart corresponding to the
|
||||
* \a event. */
|
||||
void move_to (Event const &event) const
|
||||
{ cairo->move_to (x (event.time), y (event.price)); }
|
||||
|
||||
/** Draw a line on the chart to the position corresponding to \a
|
||||
* event. */
|
||||
void line_to (Event const &event) const
|
||||
{ cairo->line_to (x (event.time), y (event.price)); }
|
||||
|
||||
|
||||
/** Set the colour for the subsequent drawing operations. */
|
||||
void set_source_rgb (Colour const &c, double const &alpha) const
|
||||
{ cairo->set_source_rgba (c.red, c.green, c.blue, alpha); }
|
||||
|
||||
/** Ditto. */
|
||||
void set_source_rgb (Colour const &c) const
|
||||
{ cairo->set_source_rgb (c.red, c.green, c.blue); }
|
||||
|
||||
|
||||
/* While this class is designed to present the application with an
|
||||
* interface expressed entirely in terms of stock market \c Event
|
||||
* points, when we are placing text onto the charts it is necessary
|
||||
* to expose the raw coordinates, for reasons involving the
|
||||
* displacement of the text position to allow for size and anchor
|
||||
* orientation. */
|
||||
|
||||
Time_Point date (int const &x) const;
|
||||
Currency_Value value (int const &y) const;
|
||||
|
||||
double x (Time_Point const &t) const;
|
||||
double y (Currency_Value const &value) const;
|
||||
|
||||
Text::Place x (Event const &e) const
|
||||
{ return {x (e.time), y (e.price)}; }
|
||||
|
||||
|
||||
|
||||
/** Add the \a message into the \a text object. */
|
||||
void add (Text &text,
|
||||
string const &message,
|
||||
Colour const &,
|
||||
Text::Place const &);
|
||||
|
||||
/** Render all the messages which have previously been added to the \a
|
||||
* text object (these will be re-arranged so that there are no
|
||||
* overlaps). */
|
||||
void render (Text &);
|
||||
|
||||
|
||||
}; /* End of class Chart_Context. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__CHART_CONTEXT__H. */
|
||||
296
trader-desk/chart-data.cc
Normal file
296
trader-desk/chart-data.cc
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/chart-data.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
constexpr const bool Chart_Data::NO_SIGNAL;
|
||||
constexpr const Currency_Value Chart_Data::NO_POSITION;
|
||||
|
||||
|
||||
void Chart_Data::timeseries__change_span (DB& db, const Duration& window)
|
||||
try
|
||||
{
|
||||
const auto start {TODAY_MARK - window};
|
||||
|
||||
bool test;
|
||||
{lock_guard l {prices_mutex};
|
||||
test = prices.empty () ? 1 : start < prices.back ().time;
|
||||
}
|
||||
|
||||
if (test)
|
||||
{
|
||||
reap_prefetch ();
|
||||
if (last_fetch_time == Time_Point {} || start < last_fetch_time)
|
||||
{
|
||||
prices.extend_range (db, company_seqid, window);
|
||||
last_fetch_time = start;
|
||||
}
|
||||
}
|
||||
|
||||
const Time_Series::Range hold {extremes};
|
||||
update_extremes (window);
|
||||
/* We are always in the GTK thread. */
|
||||
if (hold != extremes) changed_signal.emit ();
|
||||
}
|
||||
catch (Mysql::DB_Connection::Exception&) {}
|
||||
|
||||
|
||||
|
||||
static void new_timeseries (Chart_Data *const CD,
|
||||
DB& db,
|
||||
const Duration& window,
|
||||
const Duration& market_close_time)
|
||||
{
|
||||
CD->kill_prefetch ();
|
||||
CD->reap_prefetch ();
|
||||
|
||||
const auto t {chrono::system_clock::now ()};
|
||||
|
||||
const auto immediate_window
|
||||
{min (chrono::duration_cast<chrono::hours> (window),
|
||||
chrono::hours {24*50})};
|
||||
|
||||
CD->prices
|
||||
= Time_Series::from_database
|
||||
(db, CD->company_seqid, t, immediate_window, market_close_time);
|
||||
|
||||
CD->last_fetch_time = t - immediate_window;
|
||||
|
||||
CD->update_extremes (window);
|
||||
CD->changed_signal.emit ();
|
||||
|
||||
if (window != immediate_window)
|
||||
CD->prefetch_ (db.current_preferences, {window});
|
||||
}
|
||||
|
||||
void Chart_Data::new_company (DB& db,
|
||||
const int company_seqid_,
|
||||
const string& name,
|
||||
const Duration& window,
|
||||
const Duration& market_close_time)
|
||||
{
|
||||
reap_prefetch ();
|
||||
|
||||
return_subsumed ();
|
||||
subsumed_object = nullptr;
|
||||
|
||||
company_seqid = company_seqid_;
|
||||
company_name = name;
|
||||
|
||||
prices = Time_Series {market_close_time};
|
||||
extremes = Time_Series::Range {};
|
||||
extremes.start_time = chrono::system_clock::now () - window;
|
||||
|
||||
last_fetch_time = Time_Point {};
|
||||
new_timeseries (this, db, window, market_close_time);
|
||||
new_company_signal.emit ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" int emit_changed_signal (gpointer signal)
|
||||
{
|
||||
((sigc::signal<void>*) signal)->emit ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void do_prefetch
|
||||
(DB& db, Chart_Data& CD, const vector<Duration> span)
|
||||
{
|
||||
CD.prefetch_thread_stop = false;
|
||||
|
||||
for (const Duration& s : span)
|
||||
while (CD.last_fetch_time > TODAY_MARK - s)
|
||||
{
|
||||
const auto this_time
|
||||
{max (CD.last_fetch_time - chrono::hours {24}*500,
|
||||
TODAY_MARK - s)};
|
||||
|
||||
{lock_guard l {CD.prices_mutex};
|
||||
CD.prefetch_series = new Time_Series {CD.prices};
|
||||
}
|
||||
|
||||
CD.prefetch_series->extend_range
|
||||
(db, CD.company_seqid, TODAY_MARK - this_time);
|
||||
|
||||
bool need_refresh {0};
|
||||
|
||||
{lock_guard l {CD.prices_mutex};
|
||||
CD.prices = move (*CD.prefetch_series);
|
||||
CD.prefetch_series = nullptr;
|
||||
need_refresh = (CD.last_fetch_time > CD.extremes.start_time);
|
||||
CD.last_fetch_time = this_time;
|
||||
}
|
||||
|
||||
if (CD.prefetch_thread_stop) return;
|
||||
|
||||
if (need_refresh)
|
||||
{
|
||||
CD.update_extreme_prices ();
|
||||
|
||||
/* !!!! Unfortunate that this appears here--and the gtkmm
|
||||
* header--(we have nothing to do with the graphics
|
||||
* plane). Funny that it doesn't work at the point where
|
||||
* we actually enter the graphics plane, but I guess
|
||||
* other parts of the system react to this signal and
|
||||
* they in turn will trigger the graphics plane. We are
|
||||
* going to have to investigate all the points where
|
||||
* signals are emitted! (Maybe it is just occurrences of
|
||||
* this one particular signal?) */
|
||||
|
||||
/* ALWAYS outside the GTK thread. */
|
||||
gdk_threads_add_idle (emit_changed_signal, &CD.changed_signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Chart_Data::prefetch_ (Preferences& P,
|
||||
const vector<Duration>& span)
|
||||
{
|
||||
if (! prefetch_thread)
|
||||
prefetch_thread.reset (new thread ([this, span, &P]
|
||||
{ DB db {P};
|
||||
do_prefetch (db, *this, span); }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Data::reap_prefetch ()
|
||||
{
|
||||
if (! prefetch_thread) return;
|
||||
|
||||
prefetch_thread->join ();
|
||||
prefetch_thread.reset ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Data::update_extreme_prices ()
|
||||
{
|
||||
lock_guard l {prices_mutex};
|
||||
|
||||
const auto extremes_
|
||||
{prices.get_range (extremes.end_time - extremes.start_time)};
|
||||
|
||||
extremes.min_value = extremes_.min_value;
|
||||
extremes.max_value = extremes_.max_value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Data::note_current_price (DB& db, Currency_Value const &value)
|
||||
{
|
||||
{lock_guard l {prices_mutex};
|
||||
|
||||
latest_price = {chrono::system_clock::now (), value};
|
||||
|
||||
/* Expensive! */
|
||||
prices.insert (begin (prices), latest_price);
|
||||
|
||||
extremes.end_time = latest_price.time;
|
||||
}
|
||||
|
||||
update_extreme_prices ();
|
||||
|
||||
db.quick ()
|
||||
<< "update company set last_price=" << latest_price.price
|
||||
<< ", last_price_date=from_unixtime("
|
||||
<< number<chrono::seconds> (latest_price.time.time_since_epoch ())
|
||||
<< ") where seqid=" << company_seqid;
|
||||
|
||||
/* Always in GTK thread. */
|
||||
changed_signal.emit ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Data::subsume (Chart_Data *const c)
|
||||
{
|
||||
return_subsumed ();
|
||||
|
||||
subsumed_object = c;
|
||||
{
|
||||
lock_guard l {c->prices_mutex};
|
||||
lock_guard m {prices_mutex};
|
||||
|
||||
extremes = c->extremes;
|
||||
prices = c->prices;
|
||||
last_fetch_time = c->last_fetch_time;
|
||||
}
|
||||
|
||||
company_seqid = c->company_seqid;
|
||||
company_name = c->company_name;
|
||||
latest_price = c->latest_price;
|
||||
|
||||
changed_signal.emit ();
|
||||
new_company_signal.emit ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Data::return_subsumed ()
|
||||
{
|
||||
if (! subsumed_object) return;
|
||||
|
||||
kill_prefetch ();
|
||||
reap_prefetch ();
|
||||
|
||||
{
|
||||
lock_guard l {subsumed_object->prices_mutex};
|
||||
lock_guard m {prices_mutex};
|
||||
|
||||
subsumed_object->extremes = extremes;
|
||||
subsumed_object->latest_price = latest_price;
|
||||
subsumed_object->prices = prices;
|
||||
subsumed_object->last_fetch_time = last_fetch_time;
|
||||
}
|
||||
|
||||
subsumed_object = nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Data::new_event (const Event& e, const bool& no_signal)
|
||||
{
|
||||
{
|
||||
lock_guard l {prices_mutex};
|
||||
prices.insert_event (latest_price = e);
|
||||
}
|
||||
|
||||
if (e.time <= extremes.start_time) return;
|
||||
|
||||
extremes.end_time = max (extremes.end_time, e.time);
|
||||
|
||||
extremes.min_value = min (extremes.min_value, e.price);
|
||||
extremes.max_value = max (extremes.max_value, e.price);
|
||||
|
||||
if (! no_signal) { unaccurate = 0;
|
||||
changed_signal.emit (); }
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
230
trader-desk/chart-data.h
Normal file
230
trader-desk/chart-data.h
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__CHART_DATA__H
|
||||
#define DMBCS__TRADER_DESK__CHART_DATA__H
|
||||
|
||||
|
||||
#include <trader-desk/time-series.h>
|
||||
#include <sigc++/sigc++.h>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** This class looks after the data which form the graph across a
|
||||
* chart. It has several features.
|
||||
*
|
||||
* 1) It holds more data than we need, on the off-chance it might be
|
||||
* wanted later.
|
||||
*
|
||||
* 2) It actually can get as much data as are available in the database
|
||||
* in a background thread, so that they are available without delay.
|
||||
*
|
||||
* 3) One \c Chart_Data object is able to take control (subsume) the
|
||||
* data of another \c Chart_Data object. This is required as the
|
||||
* usual object creation/passing paradigms do not work when objects
|
||||
* are also widgets visible on-screen. */
|
||||
|
||||
struct Chart_Data
|
||||
{
|
||||
/** A placebo value to indicate that no actual position on the chart
|
||||
* is used. */
|
||||
static constexpr Currency_Value const NO_POSITION {-1.0};
|
||||
|
||||
|
||||
/** The database's unique identifier for this company. */
|
||||
int company_seqid;
|
||||
|
||||
/** A human-readable name string. */
|
||||
string company_name;
|
||||
|
||||
/** Access to the prices needs this mutex. */
|
||||
mutex prices_mutex;
|
||||
|
||||
/** The actual data we hold. There may be more here than we actually
|
||||
* need at the present time, i.e. they may go further back in time
|
||||
* than current analyses demand. */
|
||||
Time_Series prices {chrono::seconds {0}};
|
||||
|
||||
/** The range of data we are interested in. Will be a subset of the
|
||||
* range of \c prices. */
|
||||
Time_Series::Range extremes;
|
||||
|
||||
/** A thread to background-fetch more data. */
|
||||
unique_ptr<thread> prefetch_thread;
|
||||
|
||||
/** A holding place for the background thread to do its work. */
|
||||
Time_Series *prefetch_series {nullptr};
|
||||
|
||||
/** When this goes \c TRUE, that is taken as a signal to a background
|
||||
* thread to abandon its operations. */
|
||||
bool prefetch_thread_stop {false};
|
||||
|
||||
/** If true we consider the entire time-series to be inaccurate, and
|
||||
* show it pink. Usually used when we are in the process of updating
|
||||
* the latest known prices. */
|
||||
bool unaccurate {0};
|
||||
|
||||
/** We can't rely on the prices time-series to tell us the earliest
|
||||
* datum requested from the database, because the database might not
|
||||
* have gone back that far, and we don't want to keep requesting data
|
||||
* that don't exist. Hence we keep this record here. */
|
||||
Time_Point last_fetch_time;
|
||||
|
||||
/* !! managed entirely outside the class. */
|
||||
/** The number of shares for which analytical data are computed and
|
||||
* shown. */
|
||||
unsigned number_shares {0};
|
||||
|
||||
/** The point in the data space at which a current position was
|
||||
* opened. */
|
||||
Event open_position {0, NO_POSITION};
|
||||
|
||||
/** If the user hand-enters a price point, we store that here. */
|
||||
Event latest_price {0, NO_POSITION};
|
||||
|
||||
/** If we took the data from another object, we store the source here
|
||||
* so that it can subsequently be returned. */
|
||||
Chart_Data *subsumed_object {nullptr};
|
||||
|
||||
/** We emit this signal when any aspect of the data are changed. */
|
||||
sigc::signal<void> changed_signal;
|
||||
|
||||
/** This signal is emitted after the data have been completely
|
||||
* subsumed by those for another company. */
|
||||
sigc::signal<void> new_company_signal;
|
||||
|
||||
|
||||
|
||||
/** If the null constructor is used, then a call of new_company is the
|
||||
* only action that will make any sense; or we could subsume the data
|
||||
* of another \c Chart_Data object. */
|
||||
Chart_Data () = default;
|
||||
|
||||
|
||||
/** The sole working constructor. Sets the object up ready for
|
||||
* action, but does not actually load in any data at this point. */
|
||||
Chart_Data (int const s, string const &n,
|
||||
Duration const &market_close_time)
|
||||
: company_seqid {s},
|
||||
company_name {n},
|
||||
prices {market_close_time}
|
||||
{}
|
||||
|
||||
|
||||
/** The destructor simply cleans up all of its resources. */
|
||||
~Chart_Data ()
|
||||
{
|
||||
reap_prefetch ();
|
||||
}
|
||||
|
||||
|
||||
/** Put a flag up to instruct a running background data pre-fetch
|
||||
* thread to abandon its work and stop. */
|
||||
void kill_prefetch ()
|
||||
{
|
||||
prefetch_thread_stop = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Kick off a background thread to get data as far back in time as \a
|
||||
* span. */
|
||||
void prefetch_ (Preferences&, const vector<Duration>& span);
|
||||
|
||||
|
||||
/** Wait if necessary for the background thread to finish, and then
|
||||
* reap the new data into the existing \c prices time-series. */
|
||||
void reap_prefetch ();
|
||||
|
||||
|
||||
/** Take over the data contained in \a c. This is specifically for
|
||||
* the case when \a c is a widget on the market thumbnail page, and
|
||||
* we want to analyze the data in detail in the hand-analysis
|
||||
* page. */
|
||||
void subsume (Chart_Data *const c);
|
||||
|
||||
|
||||
/** Give the subsumed data back to the original source, leaving us
|
||||
* bereft until we subsume someone else's data. */
|
||||
void return_subsumed ();
|
||||
|
||||
|
||||
/** Assume that the range of prices inside the current range of dates
|
||||
* has changed, and update the recorded upper and lower bounds. */
|
||||
void update_extreme_prices ();
|
||||
|
||||
|
||||
/** Re-compute the \c range for the given \a window back from the
|
||||
* current time. */
|
||||
void update_extremes (Duration const &window)
|
||||
{
|
||||
lock_guard<mutex> l {prices_mutex};
|
||||
extremes = prices.get_range (window);
|
||||
}
|
||||
|
||||
|
||||
/** Add an event to the time-series corresponding to the \a value at
|
||||
* the current time. This information is also stored on the company
|
||||
* record in the database. */
|
||||
void note_current_price (DB&, Currency_Value const &value);
|
||||
|
||||
|
||||
/** Causes the span of our data to be changed in real time, i.e. not
|
||||
* in the background thread. Usually, because of the work of the
|
||||
* background thread, this function will not take very long to
|
||||
* complete the operation. */
|
||||
void timeseries__change_span (DB&, Duration const &window);
|
||||
|
||||
|
||||
/** Re-initialize the class to hold the data of company with database
|
||||
* identifier \a company_seqid. The data filling a period \a window
|
||||
* to the start of the current day will be obtained as quickly as
|
||||
* possible, but note that this might not happen as soon as the
|
||||
* function returns; a large window will be broken up and the data
|
||||
* progressively retrieved in a background thread. */
|
||||
void new_company (DB&,
|
||||
const int company_seqid,
|
||||
const string& name,
|
||||
const Duration& window,
|
||||
const Duration& market_close_time);
|
||||
|
||||
|
||||
/** Flag for the following method. */
|
||||
static constexpr bool const NO_SIGNAL {1};
|
||||
|
||||
/** Insert the new datum \a e into the \c prices time-series, and
|
||||
* update the extremes if the new point is more recent than the start
|
||||
* of the current extremes (the usual case). A \c changed_signal
|
||||
* will be emitted unless \c NO_SIGNAL is passed as the second
|
||||
* argument. */
|
||||
void new_event (Event const &e, bool const &no_signal = 0);
|
||||
|
||||
|
||||
}; /* End of class Chart_Data. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__CHART_DATA__H. */
|
||||
152
trader-desk/chart-grid.cc
Normal file
152
trader-desk/chart-grid.cc
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/chart-grid.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Chart_Grid class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
constexpr chrono::hours const Chart_Grid::DEFAULT_SPAN;
|
||||
|
||||
|
||||
Chart_Grid::Chart_Grid (Preferences& P,
|
||||
const Market_Meta_Data& m)
|
||||
: user_prefs {P}, market {m}
|
||||
{
|
||||
add_events (Gdk::BUTTON_RELEASE_MASK);
|
||||
add (table);
|
||||
DB db {user_prefs};
|
||||
regenerate (db, P, 1 /* force */);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Chart* Chart_Grid::find_chart (const int& company_seqid)
|
||||
{
|
||||
auto const c = find_if (begin (chart), end (chart),
|
||||
[company_seqid] (unique_ptr<Chart> const &x)
|
||||
{ return x->data.company_seqid
|
||||
== company_seqid; });
|
||||
|
||||
return c != end (chart) ? c->get () : nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Chart_Grid::regenerate (DB& db, Preferences& P, const bool force)
|
||||
{
|
||||
if (force || update_components (market,
|
||||
db,
|
||||
(Gtk::Window*) get_toplevel ()))
|
||||
{
|
||||
table.resize (1, 1);
|
||||
chart.clear ();
|
||||
|
||||
auto sql = db.row_query ();
|
||||
|
||||
sql << " select seqid, rtrim(name) "
|
||||
<< " from company "
|
||||
<< " where market=" << market.seqid
|
||||
<< " order by name asc";
|
||||
|
||||
sql.execute ();
|
||||
|
||||
int number_columns = int (ceil (sqrt (sql.number_rows ())));
|
||||
|
||||
chart.reserve (sql.number_rows ());
|
||||
|
||||
int row {0};
|
||||
int column {0};
|
||||
|
||||
for (; sql; ++sql)
|
||||
{
|
||||
const auto seqid {sql.next_entry<int> ()};
|
||||
|
||||
chart.emplace_back (new Chart {Chart::Style::THUMB, P});
|
||||
|
||||
/* We fix up the zero duration when the widget is exposed, so
|
||||
* that we donʼt delay getting the application started by
|
||||
* pre-loading tons of data. */
|
||||
chart.back ()->data.new_company (db,
|
||||
seqid,
|
||||
sql.next_entry<string> (),
|
||||
chrono::hours {50*24},
|
||||
market.world_data.close_time);
|
||||
|
||||
table.attach (*chart.back (), column, column + 1, row, row + 1);
|
||||
|
||||
if (0 == (column = (column+1) % number_columns)) ++row;
|
||||
}
|
||||
|
||||
show_all ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Chart_Grid::on_draw (const Cairo::RefPtr<Cairo::Context>& C)
|
||||
{
|
||||
unique_ptr<DB> db;
|
||||
|
||||
for (auto &c : chart)
|
||||
{
|
||||
if (c->data.extremes.start_time == c->data.extremes.end_time)
|
||||
{
|
||||
if (! db) db = make_unique<DB> (user_prefs);
|
||||
c->data.timeseries__change_span (*db, DEFAULT_SPAN);
|
||||
}
|
||||
else
|
||||
table.propagate_draw (*c, C);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Chart_Grid::on_button_release_event (GdkEventButton *const event)
|
||||
{
|
||||
if (chart.empty ()) return 0;
|
||||
|
||||
const Gtk::Allocation alloc {chart.front ()->get_allocation ()};
|
||||
|
||||
guint rows, columns;
|
||||
table.get_size (rows, columns);
|
||||
|
||||
const int index = int (event->x) / alloc.get_width ()
|
||||
+ columns * (int (event->y) / alloc.get_height ());
|
||||
|
||||
if (index >= 0 && index < int (chart.size ()))
|
||||
{
|
||||
selection = chart [index].get ();
|
||||
selection_signal.emit ();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
93
trader-desk/chart-grid.h
Normal file
93
trader-desk/chart-grid.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2017 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__CHART_GRID__H
|
||||
#define DMBCS__TRADER_DESK__CHART_GRID__H
|
||||
|
||||
|
||||
#include <trader-desk/chart.h>
|
||||
#include <trader-desk/markets.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Chart_Grid class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Widget which manages a whole bunch of charts, for all companies in a
|
||||
* market, and displays them all at once in a grid on the screen. */
|
||||
|
||||
struct Chart_Grid : Gtk::EventBox
|
||||
{
|
||||
Preferences& user_prefs;
|
||||
|
||||
/** The duration displayed in each thumbnail. */
|
||||
static constexpr chrono::hours const DEFAULT_SPAN {50 * 24};
|
||||
|
||||
/** Object packed with nothing but \c Chart widgets. */
|
||||
Gtk::Table table;
|
||||
|
||||
/** All the \c Chart's we are managing. */
|
||||
vector <unique_ptr <Chart>> chart;
|
||||
|
||||
/** Pointer into the \c chart list: the one we last clicked on. */
|
||||
Chart *selection {nullptr};
|
||||
|
||||
/** We emit this whenever the user clicks on a chart in the grid. */
|
||||
sigc::signal <void> selection_signal;
|
||||
|
||||
/** The market we hold and display charts for. */
|
||||
Market_Meta_Data market;
|
||||
|
||||
|
||||
/** Sole constructor, gives us a fully operational object. */
|
||||
Chart_Grid (Preferences&, const Market_Meta_Data&);
|
||||
|
||||
/** Find the chart corresponding to the company with the database \a
|
||||
* seqid, or return \c nullptr. */
|
||||
Chart *find_chart (int const &seqid);
|
||||
|
||||
/** Completely re-construct this object based on the data currently in
|
||||
* the database. If \a force is TRUE, then this object will be
|
||||
* constructed according to the information in the database; if \a
|
||||
* force is FALSE then the database may be updated with new
|
||||
* information about the market components, and if a change is made
|
||||
* in these then this object will be refreshed. */
|
||||
void regenerate (DB &, Preferences&, bool const force = 0);
|
||||
|
||||
/** Make sure all the individual charts have their data loaded before
|
||||
* we attempt to render them on-screen. */
|
||||
bool on_draw (const Cairo::RefPtr<Cairo::Context>&) override;
|
||||
|
||||
/** Called when the user selects an individual chart. We update our
|
||||
* state and emit the \c selection_signal. */
|
||||
bool on_button_release_event (GdkEventButton *const) override;
|
||||
|
||||
|
||||
}; /* End of class Chart_Grid. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__CHART_GRID__H. */
|
||||
483
trader-desk/chart.cc
Normal file
483
trader-desk/chart.cc
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/chart.h>
|
||||
#include <iomanip>
|
||||
#include <set>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Chart class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Chart::Chart (uint32_t const features_, Preferences& P)
|
||||
: features (features_)
|
||||
{
|
||||
data . changed_signal . connect ([this] { queue_draw (); });
|
||||
|
||||
/* Big enough for at least a thumb; we will take up more space if we're
|
||||
* offered it. */
|
||||
set_size_request (40, 25);
|
||||
|
||||
if (features & Feature::CROSS_HAIRS)
|
||||
add_events (Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK
|
||||
| Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
|
||||
|
||||
if (features & Feature::ANALYZERS)
|
||||
analyzer = make_unique<Analyzer_Stack> (data, P);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Chart::on_motion_notify_event (GdkEventMotion *const motion)
|
||||
{
|
||||
if (features & Feature::CROSS_HAIRS)
|
||||
{
|
||||
if (analyzer)
|
||||
analyzer->button_move (motion->x, motion->y);
|
||||
|
||||
pointer_x = (int) motion->x;
|
||||
pointer_y = (int) motion->y;
|
||||
|
||||
queue_draw ();
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Chart::on_leave_notify_event (GdkEventCrossing *const)
|
||||
{
|
||||
pointer_x = pointer_y = -1;
|
||||
queue_draw ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Chart::on_button_press_event (GdkEventButton *const event)
|
||||
{
|
||||
return features & Feature::CROSS_HAIRS
|
||||
&& analyzer
|
||||
&& analyzer->button_down (event->x, event->y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Chart::on_button_release_event (GdkEventButton *const event)
|
||||
{
|
||||
return features & Feature::CROSS_HAIRS
|
||||
&& analyzer
|
||||
&& analyzer->button_up (event->x, event->y);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* !! We really want to paint most of the chart into memory, and map it
|
||||
* to the screen as required. But we need to re-assess all the
|
||||
* tide-marks on the cursor line whenever the cursor moves. */
|
||||
|
||||
bool Chart::on_draw (const Cairo::RefPtr<Cairo::Context>& cairo)
|
||||
{
|
||||
/* This object is constructed as we draw the various aspects of the
|
||||
* chart, and then it is rendered on top of everything else right at
|
||||
* the end of this method. */
|
||||
Tide_Mark::List tide_marks;
|
||||
|
||||
|
||||
|
||||
/*************** Set up the canvas. **************************/
|
||||
|
||||
Chart_Context canvas;
|
||||
|
||||
{
|
||||
const Gtk::Allocation allocation {get_allocation ()};
|
||||
canvas.width = allocation.get_width ();
|
||||
canvas.height = allocation.get_height ();
|
||||
}
|
||||
|
||||
canvas.left_border = features & (int) Feature::AXIS_LABELS ? 45 : 4;
|
||||
canvas.bottom_border = features & (int) Feature::AXIS_LABELS ? 40 : 4;
|
||||
canvas.top_border = 4;
|
||||
canvas.right_border = 4;
|
||||
|
||||
canvas.cairo = cairo;
|
||||
|
||||
canvas.pango = Pango::Layout::create (canvas.cairo);
|
||||
canvas.pango -> set_font_description (Pango::FontDescription ("Sans 7"));
|
||||
|
||||
canvas.set_source_rgb (data.unaccurate ? Colour::NO_DATA_REGION
|
||||
: Colour::CHART_BACKGROUND);
|
||||
canvas.cairo -> paint ();
|
||||
|
||||
canvas.outline = data.extremes;
|
||||
|
||||
double const space = 0.05 * (canvas.outline.max_value
|
||||
- canvas.outline.min_value);
|
||||
|
||||
canvas.outline.min_value -= space;
|
||||
canvas.outline.max_value += space;
|
||||
|
||||
if (analyzer)
|
||||
analyzer->stretch_outline (canvas.outline);
|
||||
|
||||
auto cursor_mark
|
||||
= Tide_Mark::price_marker
|
||||
(canvas.date (pointer_x),
|
||||
canvas.outline.contains ({ canvas.date (pointer_x),
|
||||
canvas.value (pointer_y)})
|
||||
? Colour::CURSOR_TIDES
|
||||
: Colour::NO_DISPLAY);
|
||||
|
||||
auto current_mark = Tide_Mark::price_marker (canvas.outline.end_time,
|
||||
Colour::CHART_BACKGROUND);
|
||||
|
||||
canvas.cairo->set_line_width (1.0);
|
||||
|
||||
|
||||
|
||||
/************************ No-data region. *******************************/
|
||||
|
||||
{
|
||||
auto const r = data.prices.empty () ? canvas.outline.end_time
|
||||
: data.prices.back ().time;
|
||||
|
||||
if (r > canvas.outline.start_time)
|
||||
{
|
||||
canvas.set_source_rgb (Colour::NO_DATA_REGION);
|
||||
|
||||
canvas.move_to ({canvas.outline.start_time,
|
||||
canvas.outline.min_value});
|
||||
|
||||
canvas.line_to ({canvas.outline.start_time,
|
||||
canvas.outline.max_value});
|
||||
|
||||
canvas.line_to ({r, canvas.outline.max_value});
|
||||
|
||||
canvas.line_to ({r, canvas.outline.min_value});
|
||||
|
||||
canvas.cairo->fill ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********** Let the analyzers draw themselves. *************/
|
||||
|
||||
if (analyzer)
|
||||
analyzer->graph_draw_hook (canvas,
|
||||
tide_marks,
|
||||
data.number_shares,
|
||||
{cursor_mark, current_mark});
|
||||
|
||||
|
||||
/******** X-axis *******/
|
||||
|
||||
if (features & Feature::AXIS_LABELS)
|
||||
{
|
||||
canvas.set_source_rgb (Colour::TIME_AXIS);
|
||||
|
||||
char buffer [200];
|
||||
|
||||
auto const *disc = Date_Axis::discretization;
|
||||
|
||||
time_t const t = chrono::system_clock::to_time_t
|
||||
(data.extremes.end_time);
|
||||
|
||||
for (; disc->format; ++disc)
|
||||
{
|
||||
/* If the spacing between ticks is less than one pixel,
|
||||
* move on. */
|
||||
if (disc->real_interval.count ()
|
||||
/ double ((data.extremes.end_time
|
||||
- data.extremes.start_time).count ())
|
||||
* (canvas.width - canvas.left_border
|
||||
- canvas.right_border)
|
||||
< 1.0)
|
||||
continue;
|
||||
|
||||
/* Find the box needed to enclose a label showing the
|
||||
* current time. */
|
||||
strftime (buffer, sizeof (buffer),
|
||||
disc->format,
|
||||
localtime (&t));
|
||||
|
||||
canvas.pango->set_text (buffer);
|
||||
|
||||
Pango::Rectangle const rect
|
||||
= canvas.pango->get_pixel_logical_extents ();
|
||||
|
||||
/* If the spacing between ticks is more than the space
|
||||
* required to display the current date-time, then this is
|
||||
* the tick spacing we will use. */
|
||||
if (disc->real_interval.count ()
|
||||
/ double ((data.extremes.end_time
|
||||
- data.extremes.start_time).count ())
|
||||
* (canvas.width - canvas.left_border
|
||||
- canvas.right_border)
|
||||
> rect.get_width () + 4)
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
auto show_ticks = [&canvas, this]
|
||||
(Date_Axis::Discretization const &disc,
|
||||
uint32_t const &line_offset)
|
||||
{
|
||||
char buffer [200];
|
||||
|
||||
for (auto i = data.extremes.start_time;
|
||||
i <= data.extremes.end_time;
|
||||
i += disc.interval)
|
||||
{
|
||||
i = disc.round_down (i);
|
||||
|
||||
if (i > data.extremes.start_time)
|
||||
{
|
||||
auto i_ = chrono::system_clock::to_time_t (i);
|
||||
|
||||
strftime (buffer, sizeof (buffer),
|
||||
disc.format,
|
||||
localtime (&i_));
|
||||
|
||||
canvas.pango->set_text (buffer);
|
||||
|
||||
canvas.move_to ({i, canvas.outline.min_value});
|
||||
|
||||
canvas.cairo->rel_move_to (0, line_offset + 1);
|
||||
|
||||
canvas.pango->add_to_cairo_context (canvas.cairo);
|
||||
|
||||
canvas.cairo->fill ();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (disc->format)
|
||||
{
|
||||
show_ticks (*disc, 0);
|
||||
|
||||
if ((disc + 1)->format)
|
||||
{
|
||||
show_ticks (*(disc + 1), 11);
|
||||
|
||||
if ((disc + 2)->format)
|
||||
show_ticks (*(disc + 2), 22);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******** Y-axis ********/
|
||||
|
||||
canvas.pango
|
||||
->set_markup (string {"<span size=\"large\">"}
|
||||
+ pgettext ("Label", "Position value (pounds)")
|
||||
+ "</span>");
|
||||
|
||||
const Pango::Rectangle extents
|
||||
= canvas.pango->get_pixel_logical_extents ();
|
||||
|
||||
const int font_height = extents.get_height ();
|
||||
|
||||
if (features & (int) Feature::AXIS_LABELS)
|
||||
{
|
||||
canvas.cairo->save ();
|
||||
canvas.cairo->rotate (- M_PI / 2.0);
|
||||
canvas.cairo->move_to (- (canvas.height - extents.get_width ())
|
||||
/ 2,
|
||||
1);
|
||||
canvas.pango->add_to_cairo_context (canvas.cairo);
|
||||
canvas.set_source_rgb (Colour::PRICE_AXIS);
|
||||
canvas.cairo->fill ();
|
||||
canvas.cairo->restore ();
|
||||
}
|
||||
|
||||
if (features & (int) Feature::AXIS_LABELS)
|
||||
{
|
||||
double const share_scale = data.number_shares / 100.0;
|
||||
|
||||
double inc = 0.01;
|
||||
|
||||
for (inc = 0.01;
|
||||
(inc / share_scale) / (canvas.outline.max_value
|
||||
- canvas.outline.min_value)
|
||||
* (canvas.height - canvas.bottom_border
|
||||
- canvas.top_border)
|
||||
< font_height * 1.0;
|
||||
inc *= 10.0) ;
|
||||
|
||||
for (double b = inc * (floor (share_scale
|
||||
* canvas.outline.min_value
|
||||
/ inc)
|
||||
+ 1);
|
||||
b < share_scale * canvas.outline.max_value;
|
||||
b += inc)
|
||||
if (b / share_scale > canvas.outline.min_value)
|
||||
{
|
||||
ostringstream out;
|
||||
out << setw (5) << b;
|
||||
canvas.pango->set_text (out.str ());
|
||||
Pango::Rectangle const extents
|
||||
= canvas.pango->get_pixel_logical_extents ();
|
||||
canvas.move_to ({data.extremes.start_time,
|
||||
b / share_scale});
|
||||
canvas.cairo->rel_move_to (- extents.get_width () - 2,
|
||||
- extents.get_height () / 2);
|
||||
canvas.pango->add_to_cairo_context (canvas.cairo);
|
||||
canvas.set_source_rgb (Colour::PRICE_AXIS);
|
||||
canvas.cairo->fill ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (data.prices.empty ())
|
||||
{
|
||||
if (features & Feature::COMPANY_NAME)
|
||||
canvas.add (canvas.text,
|
||||
data.company_name,
|
||||
Colour::COMPANY_NAME_TITLE,
|
||||
{ canvas.left_border, canvas.top_border });
|
||||
|
||||
canvas.render (canvas.text);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
canvas.set_source_rgb (Colour::PRICE_AXIS);
|
||||
canvas.move_to ({data.extremes.start_time, canvas.outline.max_value});
|
||||
canvas.line_to ({data.extremes.start_time, canvas.outline.min_value});
|
||||
canvas.cairo->stroke ();
|
||||
|
||||
canvas.set_source_rgb (Colour::TIME_AXIS);
|
||||
canvas.move_to ({data.extremes.start_time, canvas.outline.min_value});
|
||||
canvas.line_to ({data.extremes.end_time, canvas.outline.min_value});
|
||||
canvas.cairo->stroke ();
|
||||
|
||||
{
|
||||
lock_guard<mutex> l {data.prices_mutex};
|
||||
|
||||
canvas.draw_time_series (data.prices, Colour::PRICE_GRAPH, 1.0);
|
||||
|
||||
tide_marks.emplace_back (current_mark (data.prices.front ().price,
|
||||
Colour::PRICE_TIDES));
|
||||
|
||||
tide_marks.emplace_back (cursor_mark (data.prices.interpolated_value
|
||||
(canvas.date (pointer_x)),
|
||||
Colour::PRICE_TIDES));
|
||||
}
|
||||
|
||||
if (features & Feature::CROSS_HAIRS)
|
||||
tide_marks.emplace_back (cursor_mark (canvas.value (pointer_y),
|
||||
Colour::CURSOR_TIDES));
|
||||
|
||||
|
||||
/**** Company name ****/
|
||||
|
||||
if (features & Feature::COMPANY_NAME)
|
||||
canvas.add (canvas.text,
|
||||
data.company_name,
|
||||
Colour::COMPANY_NAME_TITLE,
|
||||
canvas.x ({data.extremes.start_time,
|
||||
canvas.outline.max_value}));
|
||||
|
||||
|
||||
/***** Tide marks. *****/
|
||||
|
||||
if (features & Feature::TIDE_MARKS)
|
||||
{
|
||||
set <Time_Point> dates_shown;
|
||||
|
||||
for (auto const &tide : tide_marks)
|
||||
if (tide.temporal_colour != Colour::NO_DISPLAY
|
||||
&& tide.time >= canvas.outline.start_time
|
||||
&& tide.time <= canvas.outline.end_time)
|
||||
{
|
||||
canvas.cairo
|
||||
->set_source_rgb
|
||||
(1.0 - 0.5 * (1.0 - tide.value_colour.red),
|
||||
1.0 - 0.5 * (1.0 - tide.value_colour.green),
|
||||
1.0 - 0.5 * (1.0 - tide.value_colour.blue));
|
||||
|
||||
canvas.move_to ({canvas.outline.start_time, tide.price});
|
||||
canvas.line_to ({canvas.outline.end_time, tide.price});
|
||||
|
||||
canvas.cairo->stroke ();
|
||||
|
||||
ostringstream hold;
|
||||
hold << tide.price;
|
||||
|
||||
canvas.add (canvas.text,
|
||||
hold.str (),
|
||||
tide.value_colour,
|
||||
{canvas.x (tide.time) + 20,
|
||||
canvas.y (tide.price) - 13});
|
||||
|
||||
ostringstream hold_2;
|
||||
hold_2 << data.number_shares * tide.price / 100.0;
|
||||
|
||||
canvas.add (canvas.text,
|
||||
hold_2.str (),
|
||||
tide.value_colour,
|
||||
{canvas.x (tide.time) - 60,
|
||||
canvas.y (tide.price) - 13});
|
||||
|
||||
if (dates_shown.insert (tide.time).second)
|
||||
{
|
||||
canvas.set_source_rgb (tide.temporal_colour);
|
||||
|
||||
canvas.move_to ({tide.time, canvas.outline.max_value});
|
||||
canvas.line_to ({tide.time, canvas.outline.min_value});
|
||||
|
||||
canvas.cairo->stroke ();
|
||||
|
||||
struct tm tm;
|
||||
time_t t = chrono::system_clock::to_time_t (tide.time);
|
||||
localtime_r (&t, &tm);
|
||||
|
||||
char buffer [200];
|
||||
strftime (buffer, sizeof (buffer), "%Y-%m-%d %H:%M", &tm);
|
||||
|
||||
canvas.add (canvas.text,
|
||||
buffer,
|
||||
Colour::CURSOR_TIDES,
|
||||
{canvas.x (tide.time) + 2,
|
||||
canvas.height - canvas.bottom_border
|
||||
- 12});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canvas.render (canvas.text);
|
||||
|
||||
return 1;
|
||||
|
||||
} /* End of on_draw method. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
147
trader-desk/chart.h
Normal file
147
trader-desk/chart.h
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__CHART__H
|
||||
#define DMBCS__TRADER_DESK__CHART__H
|
||||
|
||||
|
||||
#include <functional>
|
||||
#include <trader-desk/analyzer.h>
|
||||
#include <trader-desk/date-axis.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Chart class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** This is a GTK widget which displays a chart complete with all its
|
||||
* trimmings. Most of the bulk of the implementation deals with the
|
||||
* gooey details of actually rendering all the parts into the part of
|
||||
* the screen occupied by the widget, but there is also some code to
|
||||
* support a stack of analyzers, which will pass along mouse events to
|
||||
* them if appropriate. */
|
||||
|
||||
|
||||
struct Chart : Gtk::DrawingArea
|
||||
{
|
||||
private:
|
||||
/** Set of selectable ‘trimmings’ which may adorn the chart. */
|
||||
struct Feature
|
||||
{
|
||||
/** The company name near the top-left corner. */
|
||||
uint32_t static constexpr const COMPANY_NAME = 1 << 0;
|
||||
|
||||
/** Titles and tick indicators outside the chart itself. */
|
||||
uint32_t static constexpr const AXIS_LABELS = 1 << 1;
|
||||
|
||||
/** Live cursors which follow the mouse around and display numerical
|
||||
* data at all points where the cross-hairs intersect an
|
||||
* interesting point. */
|
||||
uint32_t static constexpr const CROSS_HAIRS = 1 << 2;
|
||||
|
||||
/** Lines and numerical data highlighting interesting parts of a
|
||||
* chart. */
|
||||
uint32_t static constexpr const TIDE_MARKS = 1 << 3;
|
||||
|
||||
/** Display output of full analysis stack. */
|
||||
uint32_t static constexpr const ANALYZERS = 1 << 4;
|
||||
};
|
||||
|
||||
public:
|
||||
/** Combinations of the above features which are enabled for each
|
||||
* ‘personality’ in which charts appear. */
|
||||
struct Style
|
||||
{
|
||||
/** The appearance of charts on the market-wide thumbnail
|
||||
* display. */
|
||||
uint32_t static constexpr const THUMB = Feature::COMPANY_NAME;
|
||||
|
||||
/** The appearance of the chart shown in the detailed ‘hand
|
||||
* analysis’ widget. */
|
||||
uint32_t static constexpr const HAND_ANALYSIS = Feature::AXIS_LABELS
|
||||
+ Feature::CROSS_HAIRS
|
||||
+ Feature::TIDE_MARKS
|
||||
+ Feature::ANALYZERS;
|
||||
};
|
||||
|
||||
|
||||
/** The actual data we are presenting; our actions are driven to a
|
||||
* large extent from the data_changed signal emitted by this
|
||||
* object. */
|
||||
Chart_Data data;
|
||||
|
||||
/** An object which will embellish our chart with extra analytical
|
||||
* tools and features. */
|
||||
unique_ptr<Analyzer_Stack> analyzer;
|
||||
|
||||
private:
|
||||
/** The features we want to embellish our display with: bit-field of \c
|
||||
* Feature's via \c Style. */
|
||||
uint32_t const features;
|
||||
|
||||
/** The last known coordinates of the mouse cursor, when it was over
|
||||
* our chart; used to convey mouse place into expose() method. */
|
||||
int pointer_x, pointer_y;
|
||||
public:
|
||||
|
||||
/** Sole constructor which partially initializes an object (note in
|
||||
* particular that no company is specified here). */
|
||||
Chart (uint32_t const features_, Preferences&);
|
||||
|
||||
/** A \c Chart is a very heavy object and we do not want to be copying
|
||||
* or even moving these around. */
|
||||
Chart (Chart const &) = delete;
|
||||
Chart (Chart&&) = delete;
|
||||
void operator= (Chart const &) = delete;
|
||||
void operator= (Chart &&) = delete;
|
||||
|
||||
private:
|
||||
/** Run all of the analyzers, and then render them, the chart, and all
|
||||
* of its selected trimmings. */
|
||||
bool on_draw (const Cairo::RefPtr<Cairo::Context>&) override;
|
||||
|
||||
/** If cross-hairs are live, send the button event to the analyzer
|
||||
* stack. */
|
||||
bool on_button_press_event (GdkEventButton *const) override;
|
||||
|
||||
/** If cross-hairs are live, send the button event to the analyzer
|
||||
* stack. */
|
||||
bool on_button_release_event (GdkEventButton *const) override;
|
||||
|
||||
/** If cross-hairs are live, update all of them and get the chart
|
||||
* re-drawn. Finally pass the motion event to the analyzers. */
|
||||
bool on_motion_notify_event (GdkEventMotion *const motion) override;
|
||||
|
||||
/** If cross-hairs are live, remove them by re-drawing the chart
|
||||
* without. */
|
||||
bool on_leave_notify_event (GdkEventCrossing *const) override;
|
||||
|
||||
|
||||
}; /* End of class Chart. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__CHART__H. */
|
||||
49
trader-desk/colour.cc
Normal file
49
trader-desk/colour.cc
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/colour.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
const Colour Colour::COMPANY_NAME_TITLE {0.0, 0.0, 0.0};
|
||||
const Colour Colour::CURSOR_TIDES {0.6, 0.6, 0.6};
|
||||
const Colour Colour::PRICE_TIDES {0.4, 0.4, 1.0};
|
||||
const Colour Colour::PROFIT_LINE {0.4, 0.4, 0.4};
|
||||
const Colour Colour::POSITION_TIDES {0.0, 0.8, 0.0};
|
||||
const Colour Colour::CHART_BACKGROUND {1.0, 1.0, 1.0};
|
||||
const Colour Colour::MEAN_TIDE {1.0, 0.5, 0.5};
|
||||
const Colour Colour::MEAN_GRAPH {1.0, 0.0, 0.0};
|
||||
const Colour Colour::SD_ENVELOPE {1.0, 1.0, 0.5};
|
||||
const Colour Colour::ENVELOPE_TIDES {0.7, 0.7, 0.0};
|
||||
const Colour Colour::PRICE_GRAPH {0.2, 0.2, 0.2};
|
||||
const Colour Colour::TIME_AXIS {0.0, 0.0, 1.0};
|
||||
const Colour Colour::PRICE_AXIS {0.0, 0.9, 0.0};
|
||||
const Colour Colour::MARK_LABEL_BACK {1.0, 1.0, 1.0};
|
||||
const Colour Colour::NO_DATA_REGION {1.0, 0.8, 0.8};
|
||||
const Colour Colour::POSITIVE_DELTA {0.5, 1.0, 0.5};
|
||||
const Colour Colour::NEGATIVE_DELTA {1.0, 0.5, 0.5};
|
||||
const Colour Colour::DELTA_VALUE {0.0, 0.0, 0.0};
|
||||
|
||||
const Colour Colour::NO_DISPLAY {-1.0, -1.0, -1.0};
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
98
trader-desk/colour.h
Normal file
98
trader-desk/colour.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__COLOUR__H
|
||||
#define DMBCS__TRADER_DESK__COLOUR__H
|
||||
|
||||
|
||||
#include <cmath>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Colour class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/** A characteristic given to tide lines and text labels. */
|
||||
struct Colour
|
||||
{
|
||||
/** Component of colour, each in the range [0.0, 1.0]. */
|
||||
float red, green, blue;
|
||||
|
||||
private:
|
||||
/** Sole constructor, provide fully specified object. */
|
||||
constexpr Colour (float const &r, float const &g, float const &b)
|
||||
: red {r}, green {g}, blue {b}
|
||||
{}
|
||||
|
||||
public:
|
||||
static const Colour CURSOR_TIDES;
|
||||
static const Colour COMPANY_NAME_TITLE;
|
||||
static const Colour PRICE_TIDES;
|
||||
static const Colour PROFIT_LINE;
|
||||
static const Colour POSITION_TIDES;
|
||||
static const Colour CHART_BACKGROUND;
|
||||
static const Colour MEAN_TIDE;
|
||||
static const Colour MEAN_GRAPH;
|
||||
static const Colour SD_ENVELOPE;
|
||||
static const Colour ENVELOPE_TIDES;
|
||||
static const Colour PRICE_GRAPH;
|
||||
static const Colour TIME_AXIS;
|
||||
static const Colour PRICE_AXIS;
|
||||
static const Colour MARK_LABEL_BACK;
|
||||
static const Colour NO_DATA_REGION;
|
||||
static const Colour POSITIVE_DELTA;
|
||||
static const Colour NEGATIVE_DELTA;
|
||||
static const Colour DELTA_VALUE;
|
||||
|
||||
/** Extra-special value which indicates that this feature should not
|
||||
* be drawn at all. */
|
||||
static const Colour NO_DISPLAY;
|
||||
|
||||
|
||||
} ; /* End of class Colour. */
|
||||
|
||||
|
||||
/** Two colours compare equal if they look the same to a human observer.
|
||||
* Note that comparing anything with \c NO_DISPLAY (apart from \c
|
||||
* NO_DISPLAY itself) will fail. */
|
||||
inline constexpr bool operator== (const Colour &a, const Colour &b)
|
||||
{
|
||||
return abs (a.red - b.red) + abs (a.green - b.green)
|
||||
+ abs (a.blue - b.blue)
|
||||
< 0.01;
|
||||
}
|
||||
|
||||
|
||||
/** The exact inverse of the above function. */
|
||||
inline constexpr bool operator!= (const Colour &a, const Colour &b)
|
||||
{ return ! (a == b); }
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__COLOUR__H. */
|
||||
140
trader-desk/company-name-entry.cc
Normal file
140
trader-desk/company-name-entry.cc
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/company-name-entry.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Company_Name_Entry::Company_Name_Entry (Chart_Data &cd)
|
||||
: chart_data (cd),
|
||||
previous_icon {Gtk::Stock::GO_BACK, Gtk::ICON_SIZE_SMALL_TOOLBAR},
|
||||
next_icon {Gtk::Stock::GO_FORWARD, Gtk::ICON_SIZE_SMALL_TOOLBAR},
|
||||
label {pgettext ("Label", "Company name: ")}
|
||||
{
|
||||
Gtk::TreeModel::ColumnRecord column_record;
|
||||
column_record.add (name);
|
||||
column_record.add (seqid);
|
||||
|
||||
tree_model = Gtk::ListStore::create (column_record);
|
||||
|
||||
auto completion = Gtk::EntryCompletion::create ();
|
||||
completion->set_model (tree_model);
|
||||
completion->set_text_column (name);
|
||||
entry.set_completion (completion);
|
||||
|
||||
entry.set_width_chars (30);
|
||||
|
||||
previous_company_button . add (previous_icon);
|
||||
next_company_button . add (next_icon);
|
||||
|
||||
sub_box.pack_start (previous_company_button, Gtk::PACK_SHRINK);
|
||||
sub_box.pack_start (next_company_button, Gtk::PACK_SHRINK);
|
||||
|
||||
pack_start (sub_box, Gtk::PACK_SHRINK, 20);
|
||||
pack_start (label, Gtk::PACK_SHRINK, 0);
|
||||
pack_start (entry, Gtk::PACK_SHRINK, 0);
|
||||
|
||||
previous_company_button.signal_clicked ()
|
||||
.connect ([this] { previous_company_required (); });
|
||||
|
||||
next_company_button.signal_clicked ()
|
||||
.connect ([this] { next_company_required (); });
|
||||
|
||||
chart_data . changed_signal
|
||||
. connect ([this]
|
||||
{ if (chart_data.company_name != entry.get_text ())
|
||||
entry.set_text (chart_data.company_name); });
|
||||
|
||||
/* When the user types/selects a company name, then presses enter... */
|
||||
entry . signal_activate () . connect ([this] { do_name_select (); } );
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Company_Name_Entry::read_names (DB& db,
|
||||
size_t const &market_id,
|
||||
size_t const &company_id)
|
||||
{
|
||||
tree_model->clear ();
|
||||
|
||||
auto sql {db.row_query ()};
|
||||
|
||||
sql << " select rtrim(name), seqid "
|
||||
<< " from company "
|
||||
<< " where market=" << market_id
|
||||
<< " order by name asc";
|
||||
|
||||
cursor = begin (tree_model->children ());
|
||||
|
||||
for (sql.execute (); sql; ++sql)
|
||||
{
|
||||
auto row = tree_model->append ();
|
||||
(*row) [name] = sql.next_entry<string> ();
|
||||
(*row) [seqid] = sql.next_entry<int> ();
|
||||
|
||||
if ((*row) [seqid] == company_id)
|
||||
cursor = row;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Company_Name_Entry::do_name_select ()
|
||||
{
|
||||
cursor = find_if (begin (tree_model->children ()),
|
||||
end (tree_model->children ()),
|
||||
[this, text=entry.get_text ()]
|
||||
(Gtk::TreeRow const &x)
|
||||
{ return x [name] == text; });
|
||||
|
||||
name_change . emit ((*cursor) [seqid]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Company_Name_Entry::next_company_required ()
|
||||
{
|
||||
if (cursor != tree_model->children ().end ())
|
||||
++cursor;
|
||||
|
||||
if (cursor == tree_model->children ().end ())
|
||||
cursor = begin (tree_model->children ());
|
||||
|
||||
entry.set_text ((*cursor) [name]);
|
||||
|
||||
name_change.emit ((*cursor) [seqid]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Company_Name_Entry::previous_company_required ()
|
||||
{
|
||||
if (cursor == begin (tree_model->children ()))
|
||||
cursor = end (tree_model->children ());
|
||||
|
||||
entry.set_text ((*--cursor) [name]);
|
||||
|
||||
name_change.emit ((*cursor) [seqid]);
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
117
trader-desk/company-name-entry.h
Normal file
117
trader-desk/company-name-entry.h
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__COMPANY_NAME_ENTRY__H
|
||||
#define DMBCS__TRADER_DESK__COMPANY_NAME_ENTRY__H
|
||||
|
||||
|
||||
#include <trader-desk/chart-grid.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Company_Name_Entry class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Drop-down box allowing display and selection of names of companies
|
||||
* in a given market, plus a couple of little arrows to move backwards
|
||||
* and forwards through the list. */
|
||||
|
||||
struct Company_Name_Entry : Gtk::HBox
|
||||
{
|
||||
/** The object whose company data we are interested in. */
|
||||
Chart_Data &chart_data;
|
||||
|
||||
/** The actual on-screen widget. */
|
||||
Gtk::Entry entry;
|
||||
|
||||
/** A place to pack the forwards/backwards arrows together, for
|
||||
* aesthetic reasons. */
|
||||
Gtk::HBox sub_box;
|
||||
|
||||
/** The image on the back button. */
|
||||
Gtk::Image previous_icon;
|
||||
|
||||
/** The back button itself. */
|
||||
Gtk::Button previous_company_button;
|
||||
|
||||
/** The image on the forward button. */
|
||||
Gtk::Image next_icon;
|
||||
|
||||
/** The forward button itself. */
|
||||
Gtk::Button next_company_button;
|
||||
|
||||
/** Static label in front of the selection box. */
|
||||
Gtk::Label label;
|
||||
|
||||
/** Human-readable company names, part of \c tree_model. */
|
||||
Gtk::TreeModelColumn <Glib::ustring> name;
|
||||
|
||||
/** The database sequence ID of the companies, in correspondence with
|
||||
* the \c names above, part of \c tree_model. */
|
||||
Gtk::TreeModelColumn <unsigned> seqid;
|
||||
|
||||
/** The tree model holds a list of all company names and seqids, and
|
||||
* is used for two purposes: to provide completions to the entry
|
||||
* input box, and to provide us with an STL-like container of names,
|
||||
* e.g. for iterating through. */
|
||||
Glib::RefPtr<Gtk::ListStore> tree_model;
|
||||
|
||||
/** Pointer to the currently selected (active) item in the \c
|
||||
* tree_model. */
|
||||
Gtk::TreeModel::iterator cursor;
|
||||
|
||||
/** We emit this signal when the name is changed (selected) by the
|
||||
* user. */
|
||||
sigc::signal <void, unsigned> name_change;
|
||||
|
||||
|
||||
/** Sole constructor which composes the entire composite widget and
|
||||
* has everything ready for operation. */
|
||||
explicit Company_Name_Entry (Chart_Data &);
|
||||
|
||||
/* The various copy and move operations are deleted by default since
|
||||
* the base class doesn't support them. */
|
||||
|
||||
/** Refresh the list of company names available to scroll through, and
|
||||
* in the drop-down box. */
|
||||
void read_names (DB&, size_t const &market_id, size_t const &company_id);
|
||||
|
||||
/** The user has clicked the ‘next’ button. */
|
||||
void next_company_required ();
|
||||
|
||||
/** The user has clicked the ‘previous’ button. */
|
||||
void previous_company_required ();
|
||||
|
||||
/** The user has picked a new company name in the entry widget. */
|
||||
void do_name_select ();
|
||||
|
||||
|
||||
}; /* End of class Company_Name_Entry. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__COMPANY_NAME_ENTRY__H. */
|
||||
81
trader-desk/date-axis.cc
Normal file
81
trader-desk/date-axis.cc
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/date-axis.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk { namespace Date_Axis {
|
||||
|
||||
|
||||
inline constexpr time_t unix (Duration const &d)
|
||||
{
|
||||
return number<chrono::seconds> (d);
|
||||
}
|
||||
|
||||
|
||||
inline tm *squash (tm *const time, int count)
|
||||
{
|
||||
if (count-- > 0) time->tm_sec = 0;
|
||||
if (count-- > 0) time->tm_min = 0;
|
||||
if (count-- > 0) time->tm_hour = 0;
|
||||
if (count-- > 0) time->tm_mday = 1;
|
||||
if (count-- > 0) time->tm_mon = 0;
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
inline Duration round_count (Duration const &t, int const &count)
|
||||
{
|
||||
time_t t_ = unix (t);
|
||||
|
||||
return chrono::seconds (mktime (squash (localtime (&t_), count)));
|
||||
}
|
||||
|
||||
|
||||
Duration round_year (Duration t) { return round_count (t, 5); }
|
||||
Duration round_month (Duration t) { return round_count (t, 4); }
|
||||
|
||||
|
||||
Duration round_week (Duration t)
|
||||
{
|
||||
time_t t_ = unix (t);
|
||||
tm date = *localtime (&t_);
|
||||
|
||||
squash (&date, 3);
|
||||
|
||||
mktime (&date);
|
||||
|
||||
/* Go back to the previous Monday (tm_mday = 1). */
|
||||
if (date.tm_wday == 0)
|
||||
date.tm_mday -= 6;
|
||||
else
|
||||
date.tm_mday -= date.tm_wday - 1;
|
||||
|
||||
return chrono::seconds (mktime (&date));
|
||||
}
|
||||
|
||||
|
||||
Duration round_day (Duration t) { return round_count (t, 3); }
|
||||
Duration round_hour (Duration t) { return round_count (t, 2); }
|
||||
Duration round_minute (Duration t) { return round_count (t, 1); }
|
||||
Duration round_second (Duration t) { return t; }
|
||||
|
||||
|
||||
} } /* End of namespace DMBCS::Trader_Desk::Date_Axis. */
|
||||
133
trader-desk/date-axis.h
Normal file
133
trader-desk/date-axis.h
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__DATE_AXIS__H
|
||||
#define DMBCS__TRADER_DESK__DATE_AXIS__H
|
||||
|
||||
|
||||
#include <gtkmm.h>
|
||||
#include <trader-desk/time-series.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Really part of the implementation of \c Chart to help with drawing
|
||||
* the date axis along the bottom edge. The class provides information
|
||||
* about the possible spacings between tick marks, and methods which
|
||||
* round any date down to the nearest tick. */
|
||||
|
||||
namespace Date_Axis
|
||||
{
|
||||
|
||||
/** A \c Discretization represents a level of detail to display on a
|
||||
* time axis. The core concept is the real_interval between tick
|
||||
* marks, but this is not always consistent (lengths of months, leap
|
||||
* years), so we consider an increased \c interval which is bigger
|
||||
* than any interval which might occur and smaller than any double
|
||||
* interval, and then we use the system's calendar functions to \c
|
||||
* round_down to the last appropriate real date. */
|
||||
|
||||
struct Discretization
|
||||
{
|
||||
/** Slightly bigger than the largest real interval between ticks
|
||||
* (remember that months and years vary), but definitely smaller
|
||||
* than two ticks. */
|
||||
Duration interval;
|
||||
|
||||
/** The real interval between ticks. */
|
||||
Duration real_interval;
|
||||
|
||||
/** A \c strftime format which provides appropriate labels on the
|
||||
* ticks. A \c nullptr here represents a sentinel place-holder for
|
||||
* the end of a list of discretizations. */
|
||||
char const *const format;
|
||||
|
||||
/** Function which will round the \a Duration down to the nearest
|
||||
* tick. */
|
||||
Duration (*round_down_) (Duration);
|
||||
|
||||
/** Convenience wrapper around above function. */
|
||||
Duration round_down (Duration const &d) { return (*round_down_) (d); }
|
||||
|
||||
/** As above, but round the \c Time_Point \a t down to the nearest
|
||||
* tick. */
|
||||
Time_Point round_down (Time_Point t) const
|
||||
{
|
||||
return chrono::system_clock::from_time_t (0)
|
||||
+ (*round_down_) (t.time_since_epoch ());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* The specialized rounding functions. Since we use the C library
|
||||
* calendar functions, we always work in terms of duration (as
|
||||
* seconds) since the Unix epoch. */
|
||||
|
||||
Duration round_year (Duration t);
|
||||
Duration round_month (Duration t);
|
||||
Duration round_week (Duration t);
|
||||
Duration round_day (Duration t);
|
||||
Duration round_hour (Duration t);
|
||||
Duration round_minute (Duration t);
|
||||
Duration round_second (Duration t);
|
||||
|
||||
|
||||
namespace C = chrono;
|
||||
|
||||
|
||||
/* Null-terminated array of discretization levels which we use, in
|
||||
* increasing granularity. */
|
||||
|
||||
static constexpr const Discretization discretization []
|
||||
= { { C::seconds (1),
|
||||
C::seconds (1),
|
||||
"%H:%M:%S", &round_second },
|
||||
{ C::seconds (70),
|
||||
C::seconds (60),
|
||||
"%H:%M", &round_minute },
|
||||
{ C::seconds (6 * 60),
|
||||
C::seconds (5 * 60),
|
||||
"%H:%M", &round_minute },
|
||||
{ C::seconds (70 * 60),
|
||||
C::seconds (60 * 60),
|
||||
"%H:00", &round_hour },
|
||||
{ C::seconds (25 * 60 * 60),
|
||||
C::seconds (24 * 60 * 60),
|
||||
"%d", &round_day },
|
||||
{ C::seconds (8 * 24 * 60 * 60),
|
||||
C::seconds (7 * 24 * 60 * 60),
|
||||
"%d", &round_week },
|
||||
{ C::seconds (35 * 24 * 60 * 60),
|
||||
C::seconds (31 * 24 * 60 * 60),
|
||||
"%b", &round_month },
|
||||
{ C::seconds (380 * 24 * 60 * 60),
|
||||
C::seconds (365 * 24 * 60 * 60),
|
||||
"%Y", &round_year },
|
||||
{ C::seconds (0), C::seconds (0), nullptr, nullptr } };
|
||||
|
||||
|
||||
} /* End of namespace Date_Axis. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__DATE_AXIS__H. */
|
||||
52
trader-desk/date-range-scale.cc
Normal file
52
trader-desk/date-range-scale.cc
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/date-range-scale.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Date_Range_Scale::Date_Range_Scale (Chart_Data &d, Preferences& P)
|
||||
: Exponential_Scale (d, pgettext ("Label", "Date range = %.0f days"),
|
||||
Gdk::RGBA {"#0000ff"},
|
||||
Gdk::RGBA {"#aaaaff"},
|
||||
Gdk::RGBA {"#6666ff"},
|
||||
1, 6 * 30, P.time_horizon * 365,
|
||||
50 /* Initial setting. */),
|
||||
db {P}
|
||||
{
|
||||
d . changed_signal . connect ([this] { on_data_changed (); });
|
||||
|
||||
value_adjustment -> signal_value_changed ()
|
||||
. connect ([this] { on_value_changed (); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Date_Range_Scale::on_value_changed ()
|
||||
{
|
||||
db.check_connection ();
|
||||
data.timeseries__change_span
|
||||
(db, chrono::hours {24} * int (value ()->get_value ()));
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
51
trader-desk/date-range-scale.h
Normal file
51
trader-desk/date-range-scale.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__DATE_RANGE_SCALE__H
|
||||
#define DMBCS__TRADER_DESK__DATE_RANGE_SCALE__H
|
||||
|
||||
|
||||
#include <trader-desk/scale.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A \c Scale which controls the visual span of a \c Time_Series \c
|
||||
* Chart. */
|
||||
|
||||
struct Date_Range_Scale : Exponential_Scale
|
||||
{
|
||||
DB db;
|
||||
|
||||
/** Sole constructor which provides a fully functioning
|
||||
* object. */
|
||||
Date_Range_Scale (Chart_Data &, Preferences&);
|
||||
|
||||
/** Called when the user slides the graphical slider. */
|
||||
void on_value_changed ();
|
||||
|
||||
}; /* End of class Date_Range_Scale. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__DATE_RANGE_SCALE__H. */
|
||||
51
trader-desk/db.cc
Normal file
51
trader-desk/db.cc
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/db.h>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
DB::DB (Preferences& P) : DB_Connection {P},
|
||||
current_preferences {P}
|
||||
{
|
||||
|
||||
/* Not sure if there is some proper way to do this, but things
|
||||
* currently donʼt work unless we drop the current connection to the
|
||||
* database and make a new one. */
|
||||
/* reconnect (P); */
|
||||
}
|
||||
|
||||
|
||||
|
||||
DB& DB::check_connection ()
|
||||
{
|
||||
if (! database_equal (current_preferences, last_preferences))
|
||||
{
|
||||
last_preferences = current_preferences;
|
||||
DB_Connection::reconnect (current_preferences);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
62
trader-desk/db.h
Normal file
62
trader-desk/db.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__DB__H
|
||||
#define DMBCS__TRADER_DESK__DB__H
|
||||
|
||||
|
||||
#include <trader-desk/mysql.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the DB class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Wrapper around a database connection which ensures that the tables
|
||||
* we need are in place. */
|
||||
/* Currently hard-wired around MySQL, but the intention is to
|
||||
* abstract the database back-end. */
|
||||
|
||||
struct DB : Mysql::DB_Connection
|
||||
{
|
||||
Preferences& current_preferences;
|
||||
Preferences last_preferences;
|
||||
|
||||
|
||||
/** Establish a connection to the RDBMS, check for our database,
|
||||
* create and pre-populate tables if necessary. May throw an \c
|
||||
* Exception object. */
|
||||
DB (Preferences&);
|
||||
|
||||
|
||||
DB& check_connection ();
|
||||
|
||||
|
||||
} ; /* End of class DB. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__DB__H. */
|
||||
93
trader-desk/delta-analyzer.cc
Normal file
93
trader-desk/delta-analyzer.cc
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/delta-analyzer.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Delta_Analyzer::Delta_Analyzer (Chart_Data &cd)
|
||||
{
|
||||
cd . new_company_signal
|
||||
. connect ([this] { end_place = start_place; });
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Delta_Analyzer::graph_draw_hook
|
||||
(Chart_Context &canvas,
|
||||
Tide_Mark::List &,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &)
|
||||
{
|
||||
if (end_place.x != start_place.x || end_place.y != start_place.y)
|
||||
{
|
||||
if (mouse_active)
|
||||
{
|
||||
delta_region.start = {canvas.date (start_place.x),
|
||||
canvas.value (start_place.y)};
|
||||
|
||||
delta_region.end = {canvas.date (end_place.x),
|
||||
canvas.value (end_place.y)};
|
||||
|
||||
if (start_place.x > end_place.x)
|
||||
swap (delta_region.start, delta_region.end);
|
||||
}
|
||||
|
||||
delta_region.render (canvas, number_shares);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Delta_Analyzer::button_down (int const x, int const y)
|
||||
{
|
||||
end_place = start_place = {x, y};
|
||||
mouse_active = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Delta_Analyzer::button_move (int const x, int const y)
|
||||
{
|
||||
if (! mouse_active) return 0;
|
||||
end_place = {x, y};
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Delta_Analyzer::button_up (int const, int const)
|
||||
{
|
||||
if (! mouse_active) return 0;
|
||||
|
||||
mouse_active = 0;
|
||||
|
||||
/* Needed to remove the box from the screen if the mouse was simply
|
||||
* clicked and not moved. */
|
||||
redraw_needed . emit ();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
104
trader-desk/delta-analyzer.h
Normal file
104
trader-desk/delta-analyzer.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__DELTA_ANALYZER__H
|
||||
#define DMBCS__TRADER_DESK__DELTA_ANALYZER__H
|
||||
|
||||
|
||||
#include <trader-desk/analyzer.h>
|
||||
#include <trader-desk/colour.h>
|
||||
#include <trader-desk/delta-region.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
struct Delta_Analyzer : Analyzer
|
||||
{
|
||||
/** Transparent structure to record mouse positions in the GTK
|
||||
* window. */
|
||||
struct Place { int x, y; };
|
||||
|
||||
/** The mouse coordinates of the starting corner of the delta region
|
||||
* (where the mouse button was first pressed). */
|
||||
Place start_place {-1, -1};
|
||||
|
||||
/** The mouse coordinates of the end corner of the delta region. If
|
||||
* this equals \c start_place then the delta region is deemed not
|
||||
* active. */
|
||||
Place end_place {-1, -1};
|
||||
|
||||
/** Indicates if the mouse is currently being used to define the
|
||||
* bounds of the delta rectangle. */
|
||||
bool mouse_active {0};
|
||||
|
||||
/** Triggered internally if we need the system to re-draw the chart
|
||||
* area. */
|
||||
sigc::signal <void> redraw_needed;
|
||||
|
||||
/** The object which we are controlling, which is responsible for
|
||||
* actually displaying the delta region and its computed parameters
|
||||
* on a chart canvas. */
|
||||
Delta_Region delta_region { Colour::POSITIVE_DELTA,
|
||||
Colour::NEGATIVE_DELTA };
|
||||
|
||||
|
||||
/** Set up for operations, and watch the \a chart_data for company
|
||||
* changes when we must ‘blank’ our on-screen presence. */
|
||||
explicit Delta_Analyzer (Chart_Data &chart_data);
|
||||
|
||||
|
||||
/** If there is anything to draw then set up \c delta_region if
|
||||
* necessary and call through that object to get the actual work of
|
||||
* drawing the region done. */
|
||||
void graph_draw_hook (Chart_Context &context,
|
||||
Tide_Mark::List &,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &) override;
|
||||
|
||||
|
||||
/** Provide our signal handler. */
|
||||
sigc::signal<void> &signal_redraw_needed () override
|
||||
{ return redraw_needed; }
|
||||
|
||||
|
||||
/** Note that the mouse is now active and set (both) the corners of
|
||||
* the delta region to the position of the mouse. */
|
||||
bool button_down (int const x, int const y) override;
|
||||
|
||||
|
||||
/** If the mouse is active, set the end point of the delta region
|
||||
* definition to the mouse position. */
|
||||
bool button_move (int const x, int const y) override;
|
||||
|
||||
|
||||
/** If the mouse is active, set it to inactive and force a re-draw in
|
||||
* case the region no longer should be displayed, e.g. if the mouse
|
||||
* was just clicked. */
|
||||
bool button_up (int const x, int const y) override;
|
||||
|
||||
|
||||
} ; /* End of class Delta_Analyzer. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__DELTA_ANALYZER__H. */
|
||||
109
trader-desk/delta-region.cc
Normal file
109
trader-desk/delta-region.cc
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/delta-region.h>
|
||||
#include <iomanip>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Delta_Region class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
template<typename DURATION>
|
||||
static string readable_time_span (DURATION const &d)
|
||||
{
|
||||
namespace C = chrono;
|
||||
|
||||
ostringstream hold;
|
||||
|
||||
hold << fixed << setprecision (2);
|
||||
|
||||
if (d < C::hours {48})
|
||||
hold << (number<C::minutes> (d) / 60.0)
|
||||
<< " hours";
|
||||
else if (d < C::hours {10 * 24})
|
||||
hold << (number<C::hours> (d) / 24.0)
|
||||
<< " days";
|
||||
else
|
||||
hold << (number<C::hours> (d) / (7.0 * 24.0))
|
||||
<< " weeks";
|
||||
|
||||
return hold.str ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Delta_Region::render (Chart_Context &canvas,
|
||||
unsigned const &number_shares) const
|
||||
{
|
||||
canvas.set_source_rgb (start.price > end.price ? negative_colour
|
||||
: positive_colour);
|
||||
|
||||
canvas.move_to (start);
|
||||
canvas.line_to ({end.time, start.price});
|
||||
canvas.line_to (end);
|
||||
canvas.line_to ({start.time, end.price});
|
||||
canvas.line_to (start);
|
||||
canvas.line_to (end);
|
||||
canvas.cairo->stroke ();
|
||||
|
||||
canvas.set_source_rgb (start.price > end.price ? negative_colour
|
||||
: positive_colour,
|
||||
0.2);
|
||||
|
||||
canvas.move_to (start);
|
||||
canvas.line_to ({end.time, start.price});
|
||||
canvas.line_to (end);
|
||||
canvas.line_to ({start.time, end.price});
|
||||
canvas.cairo->fill ();
|
||||
|
||||
ostringstream percentage;
|
||||
percentage << fixed << setprecision (2)
|
||||
<< (end.price - start.price) / start.price * 100.0 << '%';
|
||||
|
||||
canvas.add (canvas.text, percentage.str (), Colour::DELTA_VALUE,
|
||||
canvas.x ({end.time, (start.price + end.price) / 2.0}));
|
||||
|
||||
ostringstream delta;
|
||||
delta << fixed << setprecision (2) << end.price - start.price;
|
||||
|
||||
canvas.add (canvas.text, delta.str (), Colour::DELTA_VALUE,
|
||||
canvas.x ({end.time, (start.price + end.price) / 2.0}));
|
||||
|
||||
ostringstream cost;
|
||||
cost << fixed << setprecision (2)
|
||||
<< number_shares * (end.price - start.price) / 100.0;
|
||||
|
||||
canvas.add (canvas.text, cost.str (), Colour::DELTA_VALUE,
|
||||
{canvas.x (start.time) - 60,
|
||||
canvas.y ((start.price + end.price) / 2.0)});
|
||||
|
||||
canvas.add (canvas.text, readable_time_span (end.time - start.time),
|
||||
Colour::DELTA_VALUE,
|
||||
{ (canvas.x (start.time) + canvas.x (end.time)) / 2,
|
||||
canvas.y (min (start.price, end.price))});
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
102
trader-desk/delta-region.h
Normal file
102
trader-desk/delta-region.h
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__DELTA_REGION__H
|
||||
#define DMBCS__TRADER_DESK__DELTA_REGION__H
|
||||
|
||||
|
||||
#include <trader-desk/chart-context.h>
|
||||
#include <trader-desk/text.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Delta_Region class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A \c Delta_Region is a rectangle marked out in (time, price) space
|
||||
* with the intention of displaying the differences (deltas) in time
|
||||
* and price along the edges and giving an indication of the rate of
|
||||
* change of the price with time. These are the quantities which
|
||||
* represent a trader‘s gains or losses, actual or potential.
|
||||
*
|
||||
* The class is only half-autonomous, it holding the extreme values
|
||||
* in (time, price) space but relying on the application to actually
|
||||
* provide and manipulate these values as required (in practice this
|
||||
* requirement is met by the \c Delta_Analyzer class). */
|
||||
|
||||
struct Delta_Region
|
||||
{
|
||||
/** The colour we use when \c start.price is _less_ than \c
|
||||
* end.price. The border will be this solid colour, and the fill
|
||||
* will be the same but with a large fraction of transparency. */
|
||||
Colour positive_colour;
|
||||
|
||||
/** The alternative to the above colour which we use when \c
|
||||
* start.price is _more_ than \c end.price. */
|
||||
Colour negative_colour;
|
||||
|
||||
/** The position of the defining point on the left (earliest time)
|
||||
* edge of the rectangle. */
|
||||
Event start;
|
||||
|
||||
/** The position of the defining point on the right (latest time)
|
||||
* edge of the rectangle. */
|
||||
Event end;
|
||||
|
||||
|
||||
|
||||
/** Partial class constructor which establishes the colours that
|
||||
* will be used to render any delta regions.
|
||||
*
|
||||
* It is left to the application to complete the specification of
|
||||
* the object: viz the \c start and \c end points. */
|
||||
Delta_Region (Colour const &p, Colour const &n)
|
||||
: positive_colour {p}, negative_colour {n}
|
||||
{}
|
||||
|
||||
|
||||
/** Partial class constructor which establishes a uni-colour
|
||||
* realization of the object on a chart.
|
||||
*
|
||||
* It is left to the application to complete the specification of
|
||||
* the object: viz the \c start and \c end points. */
|
||||
Delta_Region (Colour const &p) : Delta_Region {p, p}
|
||||
{}
|
||||
|
||||
|
||||
/** Show the region on the \a canvas along with summary data
|
||||
* relating to the fact that the display refers to \a number_shares.
|
||||
*
|
||||
* Note that the application MUST set the \c start and \c end
|
||||
* corners of the delta region before calling this method. */
|
||||
void render (Chart_Context &canvas, unsigned const &number_shares) const;
|
||||
|
||||
|
||||
} ; /* End of class Delta_Region. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__DELTA_REGION__H. */
|
||||
94
trader-desk/hand-analysis-widget.cc
Normal file
94
trader-desk/hand-analysis-widget.cc
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/hand-analysis-widget.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Hand_Analysis_Widget::Hand_Analysis_Widget
|
||||
(function<void(Chart_Data&,int const &)> gcfg,
|
||||
Preferences& P)
|
||||
: chart {Chart::Style::HAND_ANALYSIS, P},
|
||||
company_name {chart.data},
|
||||
trade {P, chart.data},
|
||||
date_range {chart.data, P},
|
||||
shares {chart.data},
|
||||
get_chart_from_grid {gcfg}
|
||||
{
|
||||
pack_start (address_bar, Gtk::PACK_SHRINK, 5);
|
||||
pack_start (display_h_box, Gtk::PACK_EXPAND_WIDGET, 5);
|
||||
|
||||
address_bar.pack_start (company_name, Gtk::PACK_EXPAND_PADDING, 0);
|
||||
address_bar.pack_start (trade, Gtk::PACK_EXPAND_PADDING, 0);
|
||||
|
||||
display_h_box.pack_start (chart, Gtk::PACK_EXPAND_WIDGET, 5);
|
||||
display_h_box.pack_start (controls_h_box, Gtk::PACK_SHRINK, 0);
|
||||
|
||||
controls_h_box.set_spacing (SCALE_SEPARATION);
|
||||
|
||||
controls_h_box.pack_start (shares, Gtk::PACK_SHRINK, 0);
|
||||
controls_h_box.pack_start (date_range, Gtk::PACK_SHRINK, 0);
|
||||
|
||||
for (auto const &w : chart.analyzer->make_control_widgets ())
|
||||
controls_h_box.pack_start (*Gtk::manage (w), Gtk::PACK_SHRINK);
|
||||
|
||||
chart . analyzer
|
||||
-> signal_redraw_needed ()
|
||||
. connect ([this] { queue_draw (); });
|
||||
|
||||
company_name . name_change
|
||||
. connect ([this, &P] (int const &seqid)
|
||||
{ get_chart_from_grid (chart.data, seqid);
|
||||
new_chart (P); });
|
||||
|
||||
} /* End of method Hand_Analysis_Widget::Hand_Analysis_Widget. */
|
||||
|
||||
|
||||
|
||||
void Hand_Analysis_Widget::new_chart (Preferences& P)
|
||||
{
|
||||
chart.data.extremes.start_time
|
||||
= chart.data.extremes.end_time
|
||||
- chrono::hours (24 * (int) date_range.value ()->get_value ());
|
||||
|
||||
chart.data . prefetch_
|
||||
(P,
|
||||
{chrono::hours {24 * (int)date_range.value ()->get_value ()},
|
||||
chrono::hours {10 * 365 * 24}});
|
||||
|
||||
chart.data.changed_signal.emit ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Hand_Analysis_Widget::subsume_selected (Chart_Grid &grid)
|
||||
{
|
||||
DB db {grid.user_prefs};
|
||||
company_name.read_names (db,
|
||||
grid.market.seqid,
|
||||
grid.selection->data.company_seqid);
|
||||
chart.data.subsume (&grid.selection->data);
|
||||
new_chart (grid.user_prefs);
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
126
trader-desk/hand-analysis-widget.h
Normal file
126
trader-desk/hand-analysis-widget.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__HAND_ANALYSIS_WIDGET__H
|
||||
#define DMBCS__TRADER_DESK__HAND_ANALYSIS_WIDGET__H
|
||||
|
||||
|
||||
#include <trader-desk/company-name-entry.h>
|
||||
#include <trader-desk/trade-instruction.h>
|
||||
#include <trader-desk/date-range-scale.h>
|
||||
#include <trader-desk/shares-scale.h>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/*
|
||||
+-----VBox-------------------------------------------------------------------------------------------+
|
||||
| +----address_bar--------------------------------------------------------------------------------+ |
|
||||
| | +--company_name-------------+ +--positions---------------------+ +--trade_instruction----+ | |
|
||||
| | | | | | | | | |
|
||||
| | +---------------------------+ +--------------------------------+ +-----------------------+ | |
|
||||
| +-----------------------------------------------------------------------------------------------+ |
|
||||
| +----display_h_box------------------------------------------------------------------------------+ |
|
||||
| | +----chart------------------------+ +----controls_h_box----------------------------------+ | |
|
||||
| | | | | +-shares-+ +-date_range--+ +--<Analyzer scales>--+ | | |
|
||||
| | | | | | | | | | | | | |
|
||||
... ... ...
|
||||
| | | | | | | | | | | | | |
|
||||
| | | | | +--------+ +-------------+ +---------------------+ | | |
|
||||
| | +---------------------------------+ +----------------------------------------------------+ | |
|
||||
| +-----------------------------------------------------------------------------------------------+ |
|
||||
+----------------------------------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/** This is a compound widget which entirely looks after itself under
|
||||
* the GTK machinery and the cooperation between various components
|
||||
* (almost all hang off the back of the chart object which throws a
|
||||
* signal whenever anything changes). The composition--layout--of the
|
||||
* widget is shown above. */
|
||||
|
||||
struct Hand_Analysis_Widget : Gtk::VBox
|
||||
{
|
||||
/** The gap between sliders in the \c controls_h_box, needed so that
|
||||
* complicated controls can emulate the appearance of this top-level
|
||||
* widget. */
|
||||
static constexpr int const SCALE_SEPARATION {10};
|
||||
|
||||
/** Layout widget as per diagram above. */
|
||||
Gtk::HBox display_h_box;
|
||||
|
||||
/** Layout widget as per diagram above. */
|
||||
Gtk::HBox controls_h_box;
|
||||
|
||||
/** Control widget as per diagram above. */
|
||||
Gtk::HBox address_bar;
|
||||
|
||||
/** The chart which we are hand-analyzing. */
|
||||
Chart chart;
|
||||
|
||||
/** Display and select the company whose data are in the \c chart. */
|
||||
Company_Name_Entry company_name;
|
||||
|
||||
/** Buy/sell button and current price input. */
|
||||
Trade_Instruction trade;
|
||||
|
||||
/** Select the range of dates over which data are shown. */
|
||||
Date_Range_Scale date_range;
|
||||
|
||||
/** Select the number of shares in a hypothetical position (if we are
|
||||
* in a position, this will be a static object which simply shows the
|
||||
* number of shares). */
|
||||
Shares_Scale shares;
|
||||
|
||||
/** Us calling this method, provided by the wider application, will
|
||||
* get the \a Chart_Data populated with the data from a chart which
|
||||
* corresponds to the company with \a seqid. */
|
||||
function<void(Chart_Data &data, int const &seqid)> get_chart_from_grid;
|
||||
|
||||
|
||||
/** Sole constructor which gives us a fully operational object. The
|
||||
* incoming function \a gcfg must provide the machinery we need in \c
|
||||
* get_chart_from_grid. */
|
||||
Hand_Analysis_Widget (function<void(Chart_Data&, int const &)> gcfg,
|
||||
Preferences&);
|
||||
|
||||
|
||||
/** Find the selected chart in the \a grid, and then subsume that
|
||||
* chart into our display: take a shadow copy of the data and show
|
||||
* these in the graph. */
|
||||
void subsume_selected (Chart_Grid &grid);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/* Implementation detail: whenever we change to charting some new
|
||||
* data we must arrange for the associated pre-fetch thread to run so
|
||||
* that the amount of data available for display matches at least the
|
||||
* time-period which the current chart spans. */
|
||||
void new_chart (Preferences&);
|
||||
|
||||
|
||||
}; /* End of class Hand_Analysis_Widget. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__HAND_ANALYSIS_WIDGET__H. */
|
||||
62
trader-desk/makefile.am
Normal file
62
trader-desk/makefile.am
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright (c) 2020 Dale Mellor
|
||||
#
|
||||
# This file is part of the trader-desk package.
|
||||
#
|
||||
# The trader-desk package is free software: you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# The trader-desk package is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
|
||||
AM_CXXFLAGS = ${gtk_config_CFLAGS} -I${top_srcdir} \
|
||||
-Wno-deprecated-copy \
|
||||
-Wall -Wextra \
|
||||
-Wno-error=int-in-bool-context \
|
||||
-Werror \
|
||||
-DCURLPP_GLOBAL_H=1 \
|
||||
-DPKGDATADIR=\"${pkgdatadir}\" \
|
||||
-DLOCALEDIR=\"${localedir}\" \
|
||||
-DHAVE_MYSQL=${HAVE_MYSQL} \
|
||||
-DHAVE_MARIADB=${HAVE_MARIADB} \
|
||||
-D_GNU_SOURCE=1 \
|
||||
-std=c++2a -I${includedir}
|
||||
|
||||
AM_LDFLAGS = ${gtk_config_LIBS}
|
||||
|
||||
bin_PROGRAMS = trader-desk
|
||||
|
||||
lib_LTLIBRARIES = libtrader-desk.la
|
||||
|
||||
CLASSES = alpha-vantage alpha-vantage--monitor analyzer application \
|
||||
chart chart-context chart-data chart-grid \
|
||||
colour company-name-entry \
|
||||
date-axis date-range-scale db delta-analyzer delta-region \
|
||||
hand-analysis-widget \
|
||||
markets moving-average-analyzer mysql \
|
||||
preferences \
|
||||
scale sd-envelope-analyzer shares-scale \
|
||||
text time-series trade-instruction \
|
||||
update-closing-prices update-latest-prices \
|
||||
wizard
|
||||
|
||||
pkginclude_HEADERS = ${CLASSES:=.h} tide-mark.h
|
||||
|
||||
nodist_noinst_HEADERS = auto-config.h
|
||||
|
||||
libtrader_desk_la_SOURCES = ${CLASSES:=.cc} \
|
||||
application--ingest-market.cc \
|
||||
application--update-closing-prices.cc
|
||||
|
||||
LDADD = libtrader-desk.la ${LTLIBINTL}
|
||||
|
||||
trader_desk_SOURCES = trader-desk.cc
|
||||
|
||||
MAINTAINERCLEANFILES = makefile.in auto-config.h.in
|
||||
332
trader-desk/markets.cc
Normal file
332
trader-desk/markets.cc
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/markets.h>
|
||||
#include <trader-desk/time-series.h> /* Only for number<> (). */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Markets::Markets (DB& db)
|
||||
{
|
||||
auto sql {db.row_query ()};
|
||||
|
||||
sql << "select seqid, symbol, name, component_extension, tracked, "
|
||||
<< " unix_timestamp(last_update), "
|
||||
<< " 60*hour(close_time)+minute(close_time) "
|
||||
<< " from market";
|
||||
|
||||
Market_Meta_Data m;
|
||||
|
||||
try
|
||||
{
|
||||
for (sql.execute (); sql; ++sql)
|
||||
{
|
||||
sql >> m.seqid >> m.world_data.symbol
|
||||
>> m.world_data.name >> m.world_data.component_extension
|
||||
>> m.tracked;
|
||||
|
||||
m.last_time = sql.next_entry ((time_t) 0);
|
||||
|
||||
m.world_data.close_time = chrono::minutes {sql.next_entry (0)};
|
||||
|
||||
this->insert ({m.seqid, m});
|
||||
}
|
||||
}
|
||||
catch (Mysql::DB_Connection::Exception&) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Markets::iterator find_symbol (Markets& markets,
|
||||
const string& symbol)
|
||||
{
|
||||
return find_if (markets.begin (), markets.end (),
|
||||
[&symbol] (const Markets::value_type& d)
|
||||
{ return d.second.world_data.symbol == symbol; });
|
||||
}
|
||||
|
||||
void update_market_meta_data (Markets& markets, DB& db)
|
||||
try
|
||||
{
|
||||
if (Market_Data_Api::short_time
|
||||
(db.scalar_result
|
||||
((time_t) 0,
|
||||
"select unix_timestamp(last_markets_update) from global")))
|
||||
return;
|
||||
|
||||
/* !!! Need to be prepared for this not to work (offline?) */
|
||||
for (const Market_Data_Api::Market& i : Market_Data_Api::get_markets ())
|
||||
if (find_symbol (markets, i.symbol) == markets.end ())
|
||||
{
|
||||
auto s {db.instruction ()};
|
||||
|
||||
s << "insert into market "
|
||||
<< " set symbol='" << i.symbol << "', "
|
||||
<< " name=\"" << i.name << "\", "
|
||||
<< " component_extension='"
|
||||
<< i.component_extension << "', "
|
||||
<< " close_time=sec_to_time("
|
||||
<< number<chrono::seconds> (i.close_time)
|
||||
<< ") ";
|
||||
|
||||
s.execute ();
|
||||
|
||||
markets.insert ({(size_t) s.insert_id (),
|
||||
{.world_data = i,
|
||||
.seqid = (size_t) s.insert_id (),
|
||||
.tracked = 0,
|
||||
.last_time = 0}});
|
||||
}
|
||||
|
||||
db.instruction ("update global "
|
||||
"set last_markets_update=from_unixtime(%d)",
|
||||
time (nullptr));
|
||||
}
|
||||
catch (Mysql::DB_Connection::Exception&) {}
|
||||
|
||||
|
||||
|
||||
bool update_components
|
||||
(Markets& markets, DB& db, Gtk::Window *const window)
|
||||
{
|
||||
/* !!! This wants to be the time on the individual market, *not* the
|
||||
* global time-stamp. */
|
||||
if (Market_Data_Api::short_time
|
||||
(db.scalar_result
|
||||
((time_t) 0,
|
||||
"select unix_timestamp(last_markets_update) from global")))
|
||||
return false;
|
||||
|
||||
update_market_meta_data (markets, db);
|
||||
|
||||
bool ret {false};
|
||||
|
||||
for (auto& m : markets)
|
||||
if (m.second.tracked)
|
||||
ret = ret || Trader_Desk::update_components
|
||||
(m.second, db, window);
|
||||
|
||||
db.instruction ("update global "
|
||||
"set last_markets_update=from_unixtime(%d)",
|
||||
time (nullptr));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void update_database
|
||||
(const Market_Data_Api::Delta& d, DB& db, const int market_seqid)
|
||||
{
|
||||
const int company_seqid
|
||||
{db.scalar_result (0,
|
||||
"select seqid "
|
||||
"from company "
|
||||
"where symbol='%s' and market=%d",
|
||||
d.symbol.c_str (),
|
||||
market_seqid)};
|
||||
|
||||
if (company_seqid)
|
||||
db.quick () << "update company "
|
||||
<< "set market=" << market_seqid
|
||||
<< " where seqid=" << company_seqid;
|
||||
else
|
||||
db.quick () << "insert into company "
|
||||
<< "set name=\"" << d.name << "\", "
|
||||
<< "symbol='" << d.symbol << "', "
|
||||
<< "market=" << market_seqid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void replace (string& in, const char a, const string& text)
|
||||
{
|
||||
for (size_t cursor {0};
|
||||
(cursor = in.find (a, cursor)) != in.npos;
|
||||
++cursor)
|
||||
in.replace (cursor, 1, text);
|
||||
}
|
||||
|
||||
static string htmlize (string in)
|
||||
{
|
||||
replace (in, '&', "&");
|
||||
replace (in, '<', "<" );
|
||||
replace (in, '>', ">" );
|
||||
|
||||
return in;
|
||||
}
|
||||
|
||||
bool update_components (Market_Meta_Data& market_data,
|
||||
DB& db,
|
||||
Gtk::Window *const window)
|
||||
{
|
||||
if (Market_Data_Api::short_time (market_data.last_time))
|
||||
return false;
|
||||
|
||||
auto instructions {Market_Data_Api::get_component_delta
|
||||
(market_data.world_data.symbol,
|
||||
market_data.last_time)};
|
||||
|
||||
market_data.last_time = time (0);
|
||||
|
||||
db.instruction ("update market "
|
||||
" set last_update=from_unixtime(%d) "
|
||||
" where seqid=%d",
|
||||
market_data.last_time, market_data.seqid);
|
||||
|
||||
if (instructions.empty ()) return false;
|
||||
|
||||
|
||||
/* List added companies for the benefit of the user. */
|
||||
string additions;
|
||||
|
||||
/* List removed companies for the benefit of the user. */
|
||||
string removals;
|
||||
|
||||
/* The total number of additions and removals. */
|
||||
size_t line_count {0};
|
||||
|
||||
/* Whether or not a terminating ‘...’ has been added to the additions. */
|
||||
bool additions_terminated {false};
|
||||
|
||||
/* Whether or not a terminating ‘...’ has been added to the removals. */
|
||||
bool removals_terminated {false};
|
||||
|
||||
auto make_updater
|
||||
= [&line_count]
|
||||
(string& line, bool& terminated)
|
||||
{
|
||||
return [&line, &terminated, &line_count]
|
||||
(string const &name, string const &symbol)
|
||||
{
|
||||
if (++line_count < 10)
|
||||
line += " " + htmlize (name)
|
||||
+ " (" + symbol + ")\n";
|
||||
|
||||
else if (! terminated)
|
||||
{
|
||||
terminated = 1;
|
||||
line += "... (more)...\n";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
auto update_additions = make_updater (additions, additions_terminated);
|
||||
auto update_removals = make_updater (removals, removals_terminated);
|
||||
|
||||
|
||||
const Market_Data_Api::Delta* move_pending {nullptr};
|
||||
|
||||
|
||||
for (auto const &i : instructions)
|
||||
{
|
||||
if (move_pending)
|
||||
{
|
||||
if (i.action != i.SIDEWAYS) break;
|
||||
|
||||
/* !!! This is a potentially dangerous operation if the same
|
||||
* symbol is used in different markets. We will have to
|
||||
* be careful how we run the server in this regard. */
|
||||
auto const company_seqid
|
||||
= db.scalar_result (0,
|
||||
"select seqid "
|
||||
"from company "
|
||||
"where symbol='%s'",
|
||||
move_pending->symbol.c_str ());
|
||||
|
||||
if (! company_seqid)
|
||||
db.instruction ("insert into company "
|
||||
"set name=\"%s\", "
|
||||
"symbol='%s', "
|
||||
"market=%d",
|
||||
i.name.c_str (), i.symbol.c_str (),
|
||||
market_data.seqid);
|
||||
|
||||
else
|
||||
db.instruction ("update company "
|
||||
"set name=\"%s\", "
|
||||
"symbol='%s', "
|
||||
"market=%d "
|
||||
"where seqid=%d",
|
||||
i.name.c_str (), i.symbol.c_str (),
|
||||
market_data.seqid, company_seqid);
|
||||
|
||||
update_additions (i.name, i.symbol);
|
||||
|
||||
move_pending = nullptr;
|
||||
}
|
||||
|
||||
|
||||
else if (i.action == i.SIDEWAYS)
|
||||
{
|
||||
move_pending = &i;
|
||||
}
|
||||
|
||||
|
||||
else if (i.action == i.ADD)
|
||||
{
|
||||
update_database (i, db, market_data.seqid);
|
||||
update_additions (i.name, i.symbol);
|
||||
}
|
||||
|
||||
else /* i.action == REMOVE */
|
||||
{
|
||||
db.quick () << "update company "
|
||||
<< "set market=0 "
|
||||
<< "where symbol='" << i.symbol << "' "
|
||||
<< "and market=" << market_data.seqid;
|
||||
|
||||
update_removals (i.name, i.symbol);
|
||||
}
|
||||
}
|
||||
|
||||
if (! window) return true;
|
||||
|
||||
string a = "<b>MARKET MOVEMENTS</b>\n\n<u>"
|
||||
+ market_data.world_data.name + "</u>\n";
|
||||
|
||||
if (additions.length ())
|
||||
a += "<span color=\"green\">New entries</span>\n" + additions;
|
||||
|
||||
if (removals.length ())
|
||||
a += "<span color=\"red\">Dropped entries</span>\n" + removals;
|
||||
|
||||
Gtk::MessageDialog {*window, a, 1/*use mark-up*/} . run ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void start_tracking (Markets& markets, DB& db, const string& symbol)
|
||||
{
|
||||
const Markets::iterator m {find_symbol (markets, symbol)};
|
||||
|
||||
if (m == markets.end ()) return;
|
||||
|
||||
m->second.tracked = 1;
|
||||
|
||||
db.instruction ("update market set tracked=1 where seqid=%d", m->first);
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
119
trader-desk/markets.h
Normal file
119
trader-desk/markets.h
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__MARKETS__H
|
||||
#define DMBCS__TRADER_DESK__MARKETS__H
|
||||
|
||||
|
||||
#include <dmbcs-market-data-api.h>
|
||||
#include "db.h"
|
||||
#include <gtkmm.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Market_Meta_Data class and the \c Markets
|
||||
* class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Really just a data-carrying structure (vessel) which fully describes
|
||||
* a stock market. It is divided into two parts: data which describe
|
||||
* the markets in the eyes of the outside world, and data which
|
||||
* describe the state of the markets from our own, private, point of
|
||||
* view. */
|
||||
struct Market_Meta_Data
|
||||
{
|
||||
/** The subset of data which relate to the state of affairs in the
|
||||
* world beyond this application; these are obtained from an
|
||||
* Internet server through the medium of the
|
||||
* dmbcs-market-data-api library. */
|
||||
Market_Data_Api::Market world_data;
|
||||
|
||||
/** The sequence ID of this market in our local database. */
|
||||
size_t seqid;
|
||||
|
||||
/** Whether we are keeping a database of prices for this
|
||||
* market. */
|
||||
bool tracked;
|
||||
|
||||
/** The last time the component data for this market were
|
||||
* synchronized with a market data server. */
|
||||
time_t last_time;
|
||||
|
||||
}; /* End of class Market_Meta_Data. */
|
||||
|
||||
|
||||
|
||||
void update_database
|
||||
(const Market_Data_Api::Delta&, DB&, const int market_seqid);
|
||||
|
||||
|
||||
|
||||
/** Fetch the data for this market from the Internet and update the
|
||||
* database if necessary. A progress indication may be provided in a
|
||||
* dialog on top of the window. */
|
||||
bool update_components (Market_Meta_Data&, DB&, Gtk::Window *const);
|
||||
|
||||
|
||||
|
||||
/** A self-building collection of all markets known to the on-line data
|
||||
* server, indexed according to our local database sequence ID. */
|
||||
struct Markets : map <size_t, Market_Meta_Data>
|
||||
{
|
||||
/** Sole constructor, which self-builds our \c map from information
|
||||
* contained in the local \a database. */
|
||||
explicit Markets (DB& database);
|
||||
|
||||
/** Constructing these objects is expensive; allow them to be moved
|
||||
* around but not duplicated. */
|
||||
Markets () = delete;
|
||||
Markets (Markets const &) = delete;
|
||||
Markets (Markets &&) = default;
|
||||
Markets &operator= (Markets const &) = delete;
|
||||
Markets &operator= (Markets &&) = default;
|
||||
|
||||
} ; /* End of class Markets. */
|
||||
|
||||
|
||||
/** Market the market with \a symbol as being tracked, both in memory
|
||||
* (here) and in the database. Note that this does NOT cause any price
|
||||
* data to be fetched from the server. */
|
||||
void start_tracking (Markets&, DB&, const string& symbol);
|
||||
|
||||
/** Get information on all known markets from the server, and update our
|
||||
* own information and the database. */
|
||||
void update_market_meta_data (Markets&, DB&);
|
||||
|
||||
/** Update the meta-data with the above method, then update the
|
||||
* components of each market we follow (note *not* the price
|
||||
* information!) These actions will not be allowed to take place more
|
||||
* than once per 12 hours. The return value indicates if anything has
|
||||
* actually changed. If a window is given, provide a report on changes
|
||||
* in a dialog box on the window. */
|
||||
bool update_components (Markets *const, Gtk::Window *const = nullptr);
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__MARKETS__H. */
|
||||
128
trader-desk/moving-average-analyzer.cc
Normal file
128
trader-desk/moving-average-analyzer.cc
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/moving-average-analyzer.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Moving_Average_Analyzer class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Moving_Average_Analyzer::Moving_Average_Analyzer (Chart_Data &cd)
|
||||
: chart_data (cd),
|
||||
mean_series {cd.prices.market_close_time}
|
||||
{
|
||||
chart_data . changed_signal . connect ([this] { compute (); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
vector <Gtk::Widget*> Moving_Average_Analyzer::make_control_widgets ()
|
||||
{
|
||||
auto *const scale
|
||||
= new Scale {chart_data,
|
||||
(double) (number<chrono::hours> (mean_window) / 24)};
|
||||
|
||||
scale -> value ()
|
||||
-> signal_value_changed ()
|
||||
. connect ([this, scale] { control_moved (scale); });
|
||||
|
||||
return {scale};
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Moving_Average_Analyzer::stretch_outline (Time_Series::Range &outline)
|
||||
{
|
||||
/* !!!! When we re-visit this, need to ensure that the mean
|
||||
* time-series has been previously computed. */
|
||||
|
||||
auto const range = mean_series.get_range ();
|
||||
|
||||
auto const margin = (std::max (outline.max_value, range.max_value)
|
||||
- std::min (outline.min_value, range.min_value))
|
||||
* 0.05;
|
||||
|
||||
outline.max_value = max (outline.max_value, range.max_value + margin);
|
||||
|
||||
outline.min_value = min (outline.min_value, range.min_value - margin);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Moving_Average_Analyzer::graph_draw_hook
|
||||
(Chart_Context &canvas,
|
||||
Tide_Mark::List &marks,
|
||||
unsigned,
|
||||
vector <Tide_Mark::Price_Marker> const &markers)
|
||||
{
|
||||
canvas . draw_time_series (mean_series, Colour::MEAN_GRAPH, 0.5);
|
||||
|
||||
|
||||
/* The vertical bar which shows the mid-point of the latest window. */
|
||||
canvas . set_source_rgb (Colour::MEAN_GRAPH);
|
||||
|
||||
canvas . move_to ({canvas.outline.end_time - mean_window / 2,
|
||||
canvas.outline.min_value});
|
||||
|
||||
canvas . line_to ({canvas.outline.end_time - mean_window / 2,
|
||||
canvas.outline.max_value});
|
||||
|
||||
canvas . cairo -> stroke ();
|
||||
|
||||
|
||||
/* Put a tide-mark at the mean value at all points in time at which a
|
||||
* marker has been specified. */
|
||||
for (auto const &marker : markers)
|
||||
marks.emplace_back (marker (mean_series.interpolated_value
|
||||
(marker (0.0, Colour::MEAN_TIDE).time),
|
||||
Colour::MEAN_TIDE));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Moving_Average_Analyzer::control_moved (Scale const *const scale)
|
||||
{
|
||||
mean_window = chrono::hours ((int) scale->value ()->get_value () * 24);
|
||||
compute ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Moving_Average_Analyzer::compute ()
|
||||
{
|
||||
{
|
||||
lock_guard<mutex> l {chart_data.prices_mutex};
|
||||
|
||||
mean_series = Time_Series::compute_moving_average
|
||||
(chart_data.prices,
|
||||
mean_window,
|
||||
chart_data.extremes.start_time);
|
||||
}
|
||||
|
||||
redraw_needed_.emit ();
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
125
trader-desk/moving-average-analyzer.h
Normal file
125
trader-desk/moving-average-analyzer.h
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__MOVING_AVERAGE_ANALYZER__H
|
||||
#define DMBCS__TRADER_DESK__MOVING_AVERAGE_ANALYZER__H
|
||||
|
||||
|
||||
#include <trader-desk/analyzer.h>
|
||||
#include <trader-desk/scale.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Moving_Average_Analyzer class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** An \c Analyzer which computes and displays a smoothed version of a
|
||||
* prices time-series. */
|
||||
|
||||
struct Moving_Average_Analyzer : Analyzer
|
||||
{
|
||||
|
||||
/** The widget which we proffer to control ourselves is nothing more
|
||||
* than an exponential scale, calibrated to allow specification of
|
||||
* averaging windows between one day and a year, with fine control
|
||||
* out to 14 days. */
|
||||
|
||||
struct Scale : Exponential_Scale
|
||||
{
|
||||
Scale (Chart_Data &chart_data, double const &initial_value)
|
||||
: Exponential_Scale (chart_data,
|
||||
pgettext ("Label",
|
||||
"Mean window = %.0f days"),
|
||||
Gdk::RGBA {"#ff0000"},
|
||||
Gdk::RGBA {"#ffaaaa"},
|
||||
Gdk::RGBA {"#ff6666"},
|
||||
1, 14, 365,
|
||||
initial_value)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
/** The data that we are to analyze. */
|
||||
Chart_Data &chart_data;
|
||||
|
||||
/** The size of the window over which we compute means. */
|
||||
Duration mean_window {chrono::hours {14*24}};
|
||||
|
||||
/** The resulting time-series of local mean values. */
|
||||
Time_Series mean_series;
|
||||
|
||||
/** Fired whenever the analysis of data produces new results, which will
|
||||
* need rendering in the GUI. */
|
||||
sigc::signal <void> redraw_needed_;
|
||||
|
||||
|
||||
/** Called when the user slides the control on the \a
|
||||
* exponential_scale, meaning to change the moving-average window. */
|
||||
void control_moved (Scale const *const);
|
||||
|
||||
/** Called whenever we must re-compute the moving-average
|
||||
* time-series, including when the \c chart_data change. */
|
||||
void compute ();
|
||||
|
||||
|
||||
/** Sole constructor which registers the \a chart_data we are to
|
||||
* analyze. */
|
||||
explicit Moving_Average_Analyzer (Chart_Data &);
|
||||
|
||||
|
||||
/********************** Analyzer interface. ****************************/
|
||||
|
||||
|
||||
/** Make a single widget which controls the size of the moving average
|
||||
* window. */
|
||||
vector <Gtk::Widget*> make_control_widgets () override;
|
||||
|
||||
|
||||
/** Draw the \c mean_series and add a \a tide label at all the \a
|
||||
* marked points in time. */
|
||||
void graph_draw_hook (Chart_Context &,
|
||||
Tide_Mark::List &tide,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &marked)
|
||||
override;
|
||||
|
||||
|
||||
/** Make sure the \a range includes the entire mean series, with room
|
||||
* to breathe. */
|
||||
void stretch_outline (Time_Series::Range &range) override;
|
||||
|
||||
|
||||
/** Return our signal so that the application can connect and act when
|
||||
* we need a re-draw to take place. */
|
||||
sigc::signal <void> &signal_redraw_needed () override
|
||||
{ return redraw_needed_; }
|
||||
|
||||
|
||||
}; /* End of class Moving_Average_Analyzer. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__MOVING_AVERAGE_ANALYZER__H. */
|
||||
163
trader-desk/mysql.cc
Normal file
163
trader-desk/mysql.cc
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/mysql.h>
|
||||
#include <iostream>
|
||||
#include <cstdio>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of class methods in \c Mysql namespace. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk { namespace Mysql {
|
||||
|
||||
|
||||
int Instruction::execute ()
|
||||
{
|
||||
const string query {buffer->str ()};
|
||||
const int test {mysql_real_query (mysql, query.c_str(), query.length())};
|
||||
|
||||
if (test && ! no_error)
|
||||
{
|
||||
cerr << "MYSQL ERROR: " << mysql_error (mysql) << endl;
|
||||
throw DB_Connection::Exception {mysql_error (mysql)};
|
||||
}
|
||||
|
||||
return test;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DB_Connection::connect (const Preferences& P)
|
||||
{
|
||||
if (! mysql_real_connect (&mysql,
|
||||
P.database_host.data (),
|
||||
P.database_user.data (),
|
||||
P.database_password.data (),
|
||||
P.database_instance.data (),
|
||||
P.database_port,
|
||||
P.database_socket.data (),
|
||||
0/*flags*/))
|
||||
throw Exception {mysql_error (&mysql)};
|
||||
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DB_Connection::reconnect (const Preferences& P)
|
||||
{
|
||||
if (initialized) mysql_close (&mysql);
|
||||
initialized = 0;
|
||||
connect (P);
|
||||
}
|
||||
|
||||
|
||||
|
||||
DB_Connection::DB_Connection (const Preferences& P)
|
||||
{
|
||||
mysql_init (&mysql);
|
||||
connect (P);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int _run_query (MYSQL *const mysql,
|
||||
string const &_template,
|
||||
va_list arguments)
|
||||
{
|
||||
int buffer_length = _template.length () + 500;
|
||||
|
||||
char *buffer = new char [buffer_length];
|
||||
|
||||
int length;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
length = vsnprintf
|
||||
(buffer, buffer_length, _template.c_str (), arguments);
|
||||
|
||||
if (length == buffer_length)
|
||||
{
|
||||
buffer_length += 1000;
|
||||
delete[] buffer;
|
||||
buffer = new char [buffer_length];
|
||||
}
|
||||
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
int const test = mysql_real_query (mysql, buffer, length);
|
||||
|
||||
delete[] buffer;
|
||||
|
||||
return test;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DB_Connection::void_database_result (MYSQL *const mysql,
|
||||
string const &template_,
|
||||
va_list arguments)
|
||||
{
|
||||
if (_run_query (mysql, template_, arguments) != 0)
|
||||
cerr << mysql_error (mysql) << endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
string DB_Connection::string_database_result (MYSQL *const mysql,
|
||||
string const &template_,
|
||||
va_list arguments)
|
||||
{
|
||||
if (_run_query (mysql, template_, arguments) != 0)
|
||||
{
|
||||
cerr << mysql_error (mysql) << endl;
|
||||
return string {};
|
||||
}
|
||||
|
||||
MYSQL_RES *const results = mysql_store_result (mysql);
|
||||
|
||||
if (! results)
|
||||
return string {};
|
||||
|
||||
MYSQL_ROW const row = mysql_fetch_row (results);
|
||||
|
||||
string const ret = (row && row [0]) ? string {row [0]} : string {};
|
||||
|
||||
mysql_free_result (results);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void DB_Connection::instruction (string const &template_, ...)
|
||||
{
|
||||
va_list args; va_start (args, template_);
|
||||
void_database_result (&mysql, template_, args);
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
|
||||
} } /* End of namespace DMBCS::Trader_Desk::Mysql. */
|
||||
508
trader-desk/mysql.h
Normal file
508
trader-desk/mysql.h
Normal file
|
|
@ -0,0 +1,508 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__MYSQL__H
|
||||
#define DMBCS__TRADER_DESK__MYSQL__H
|
||||
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <trader-desk/preferences.h>
|
||||
|
||||
#if HAVE_MYSQL
|
||||
# include <mysql/mysql.h>
|
||||
#endif
|
||||
|
||||
#if HAVE_MARIADB
|
||||
# include <mariadb/mysql.h>
|
||||
#endif
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Definition of \c Mysql::DB_Connection, \c Mysql::Instruction, \c
|
||||
* Mysql::Quick_Instruction, \c Mysql::Simple_Query and \c
|
||||
* Mysql::Row_Query, objects which capture between them all the specifics
|
||||
* of operating a MySQL/MariaDB database. */
|
||||
|
||||
/* Note that it is the intention to base these classes on abstract ones,
|
||||
* and thence to provide alternative database back-ends. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/** This namespace encaptures everything which is specific to a
|
||||
* MySQL/MariaDB database back-end (specifically \c libmysqlclient).
|
||||
* Nothing outside should have any inkling of the fact that we are using
|
||||
* such a database. */
|
||||
namespace Mysql {
|
||||
|
||||
|
||||
/** Object which provides std::ostream-type features to develop an SQL
|
||||
* query string (should be one which produces no useful results) and
|
||||
* then allows for its execution. If this produces some unique \c
|
||||
* seqid (e.g. by inserting into a table with an automatic index
|
||||
* column), then that value can subsequently be obtained with the \c
|
||||
* insert_id method. */
|
||||
|
||||
struct Instruction
|
||||
{
|
||||
/** The connection to the database which we will be using. */
|
||||
MYSQL *mysql;
|
||||
|
||||
/** Flag to indicate if we should print any error messages (\c FALSE)
|
||||
* or not. */
|
||||
bool const no_error;
|
||||
|
||||
/** Place where we accumulate the query string. */
|
||||
unique_ptr <ostringstream> buffer;
|
||||
|
||||
|
||||
/** Flag for the constructor which allows to specify that no error
|
||||
* messages should be printed. */
|
||||
enum : bool { NO_ERROR = true };
|
||||
|
||||
/** Sole constructor which takes a connection to the database and an
|
||||
* optional flag (see above) to indicate no error messages should
|
||||
* appear. */
|
||||
explicit Instruction (MYSQL *const m, bool const ne = false)
|
||||
: mysql {m},
|
||||
no_error {ne},
|
||||
buffer {make_unique<ostringstream> ()}
|
||||
{}
|
||||
|
||||
|
||||
/** We can't copy these objects as that would wreak havoc with the \c
|
||||
* buffer, but we want to be able to move them so they can be passed
|
||||
* back from factory functions. */
|
||||
Instruction (Instruction const &) = delete;
|
||||
Instruction (Instruction &&) = default;
|
||||
Instruction &operator= (Instruction const &) = delete;
|
||||
Instruction &operator= (Instruction &&) = default;
|
||||
|
||||
|
||||
/** This is the method which makes our class look like an
|
||||
* std::ostream. */
|
||||
template <typename T>
|
||||
Instruction &operator<< (T const &i) { *buffer << i; return *this; }
|
||||
|
||||
/** Send the query string, which should by now have been sent into the
|
||||
* \c buffer, to the database and return the resulting status (zero
|
||||
* is success) of the operation. */
|
||||
int execute ();
|
||||
|
||||
/** If the query caused an auto-incrementing table column to be
|
||||
* updated, this method will return the last value assigned. */
|
||||
int insert_id () { return mysql_insert_id (mysql); }
|
||||
|
||||
} ; /* End of class Instruction. */
|
||||
|
||||
|
||||
|
||||
/** A \c Quick_Instruction is just a \c Instruction which
|
||||
* executes the query on object destruction, allowing for one-line
|
||||
* instructions to make modifications to the database,
|
||||
* e.g. `Quick_Instruction {} << "update table ...";'. */
|
||||
struct Quick_Instruction : Instruction
|
||||
{
|
||||
/** All construction, move, no-copying construction is exactly as \c
|
||||
* Instruction. */
|
||||
using Instruction::Instruction;
|
||||
|
||||
Quick_Instruction (Quick_Instruction const &) = delete;
|
||||
Quick_Instruction (Quick_Instruction &&m) = default;
|
||||
Quick_Instruction &operator= (Quick_Instruction const &) = delete;
|
||||
Quick_Instruction &operator= (Quick_Instruction &&) = delete;
|
||||
|
||||
|
||||
/** Do the work in the destructor. */
|
||||
~Quick_Instruction () { Instruction::execute (); }
|
||||
|
||||
/* Make sure the user can't accidentally call the execution
|
||||
* directly. */
|
||||
int execute () = delete;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** A \c Simple_Query is like a \c Instruction which returns
|
||||
* a single meaningful value. The usage pattern is to construct,
|
||||
* assemble a query with the inherited \c operator<<, and then call \c
|
||||
* return_scalar to get the solitary resulting value. */
|
||||
struct Simple_Query : Instruction
|
||||
{
|
||||
/** Construction, move, non-copy is exactly as for \c
|
||||
* Instruction. */
|
||||
using Instruction::Instruction;
|
||||
|
||||
Simple_Query (Simple_Query const &) = delete;
|
||||
Simple_Query (Simple_Query &&) = default;
|
||||
Simple_Query &operator= (Simple_Query const &) = delete;
|
||||
Simple_Query &operator= (Simple_Query &&) = default;
|
||||
|
||||
/** Execute the assembled query and return the result, cast to type \c
|
||||
* T. The \a fallback both determines the return type and also the
|
||||
* value that will be returned if the database fails to provide
|
||||
* this. */
|
||||
template <typename T>
|
||||
T return_scalar (T const &fallback = T ());
|
||||
|
||||
/** Make sure the user can't accidentally call for the execution
|
||||
* directly. */
|
||||
int execute () = delete;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
/** This class represents a database query which produces (selects)
|
||||
* multiple rows of multiple columns of results. It is used exactly as
|
||||
* a \c Instruction, but after calling the \c execute method the
|
||||
* class provides iterators and other convenience access methods for
|
||||
* retrieving the data. */
|
||||
class Row_Query : public Instruction
|
||||
{
|
||||
/** The result from the database query. Note that this is a resource
|
||||
* we manage locally, so have to be careful with class move and
|
||||
* destruction. */
|
||||
MYSQL_RES *result {nullptr};
|
||||
|
||||
/** An index into the rows of the \c result. */
|
||||
MYSQL_ROW row;
|
||||
|
||||
/** An index into the columns of the \c row we are currently
|
||||
* examining. */
|
||||
int next_index;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** Construction, move and non-copy is the same as for the base
|
||||
* classes, but we have to take care with the handling of our
|
||||
* controlled resource: the \c result. */
|
||||
using Instruction::Instruction;
|
||||
|
||||
Row_Query (Row_Query const &) = delete;
|
||||
|
||||
Row_Query (Row_Query &&m)
|
||||
: Instruction {move (m)},
|
||||
result {m.result}, row {m.row}, next_index {m.next_index}
|
||||
{ m.result = nullptr; }
|
||||
|
||||
Row_Query &operator= (Row_Query const &) = delete;
|
||||
|
||||
Row_Query &operator= (Row_Query &&m)
|
||||
{
|
||||
result = m.result; m.result = nullptr;
|
||||
row = m.row;
|
||||
next_index = m.next_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
~Row_Query () { if (result) mysql_free_result (result); }
|
||||
|
||||
|
||||
/** Perform the database query, and then obtain a result manager,
|
||||
* fetch the first row of results, and set up the result indexers to
|
||||
* indicate that the first column of the first row will be the next
|
||||
* available result value. */
|
||||
Row_Query &execute () { Instruction::execute ();
|
||||
result = mysql_store_result (mysql);
|
||||
row = result ? mysql_fetch_row (result) : 0;
|
||||
next_index = 0;
|
||||
return *this; }
|
||||
|
||||
|
||||
/** After \c execute has been called, return the number of rows of
|
||||
* data that are available. */
|
||||
int number_rows () const { return mysql_num_rows (result); }
|
||||
|
||||
|
||||
/** Skip over \a count columns in the current row. */
|
||||
Row_Query &skip_entry (const int count = 1) { next_index += count;
|
||||
return *this; }
|
||||
|
||||
|
||||
/** Read the next result value into \a ret, and advance the indexers
|
||||
* to the next column. */
|
||||
template <typename T>
|
||||
Row_Query &operator>> (T &ret)
|
||||
{
|
||||
istringstream in (row [next_index++]);
|
||||
in >> ret;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
/** Return the next value cast to type \c T, returning \a fallback if
|
||||
* the database did not provide a valid value, and advancing the
|
||||
* index along to the next column so that subsequent calls to this
|
||||
* method automatically iterate through the values of the row. This
|
||||
* method can be use interchangeably with the previous one (\c
|
||||
* operator>>), according to convenience. */
|
||||
template <typename T> T next_entry (const T& fallback = {})
|
||||
{
|
||||
if (row [next_index] == 0) { ++next_index;
|
||||
return fallback; }
|
||||
|
||||
T ret;
|
||||
*this >> ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** Return \c TRUE if there are more rows to reap data for. */
|
||||
operator bool () const { return row; }
|
||||
|
||||
|
||||
/** Iterate to the next row in the result data set. Combined with the
|
||||
* above result this allows for the straight-forward implementation
|
||||
* of \c for(;;) loops over all the rows in a result set. */
|
||||
void operator++ () { row = mysql_fetch_row (result);
|
||||
next_index = 0; }
|
||||
|
||||
|
||||
} ; /* End of class Row_Query. */
|
||||
|
||||
|
||||
|
||||
/** Essentially just a MYSQL object which automatically opens and closes
|
||||
* a connection to the \c trader-desk database on object construction
|
||||
* and destruction.
|
||||
*
|
||||
* The sole constructor may throw an exception if a database connection
|
||||
* cannot be established. The copy constructor is implicitly deleted,
|
||||
* the move constructors implicitly defined.
|
||||
*
|
||||
* Once the connection is established, the class provides factory
|
||||
* methods for the above query object types, plus a couple of
|
||||
* convenience functions which allow for one-line printf-style query
|
||||
* specification and execution.
|
||||
*
|
||||
* Note that most of the connection parameters come through the
|
||||
* config.h file, except the password which is passed directly on the
|
||||
* compiler command line, via makefile.am and configure.ac; we do not
|
||||
* store it in any source files, hence it doesn't get into GIT, but do
|
||||
* beware that the raw string is present in built binaries and, no
|
||||
* doubt, in infrastructure files in the package build directory. */
|
||||
|
||||
struct DB_Connection
|
||||
{
|
||||
/** Thrown if a connection to the RDBMS cannot be made. */
|
||||
struct Exception : runtime_error
|
||||
{ using runtime_error::runtime_error; };
|
||||
|
||||
/** The real connection object which we are wrapping. */
|
||||
MYSQL mysql;
|
||||
bool initialized {0};
|
||||
|
||||
|
||||
/** Establish a connection to the RDBMS. May throw an \c Exception
|
||||
* object. */
|
||||
explicit DB_Connection (const Preferences&);
|
||||
|
||||
|
||||
/** We only want one of these for the application, so copy and move
|
||||
* are irrelevant and are \c delete'd. */
|
||||
DB_Connection (DB_Connection const &) = delete;
|
||||
DB_Connection (DB_Connection &&m) = delete;
|
||||
DB_Connection &operator= (DB_Connection const &) = delete;
|
||||
DB_Connection &operator= (DB_Connection &&m) = delete;
|
||||
|
||||
|
||||
/** Close the connection to the database. */
|
||||
~DB_Connection () { if (initialized) mysql_close (&mysql); }
|
||||
|
||||
|
||||
/** Manufacture an \c Instruction. */
|
||||
Instruction instruction () { return Instruction {&mysql}; }
|
||||
|
||||
/** Manufacture a \c Quick_Instruction. */
|
||||
Quick_Instruction quick () { return Quick_Instruction {&mysql};}
|
||||
|
||||
/** Manufacture a \c Simple_Query. */
|
||||
Simple_Query simple_query () { return Simple_Query {&mysql}; }
|
||||
|
||||
/** Manufacture a \c Row_Query. */
|
||||
Row_Query row_query () { return Row_Query {&mysql}; }
|
||||
|
||||
|
||||
|
||||
/** Implementation of following (\c instruction) method. */
|
||||
static void void_database_result (MYSQL *const mysql,
|
||||
string const &template_,
|
||||
va_list arguments);
|
||||
|
||||
/** Execute a one-shot SQL statement on the database, expressed
|
||||
* through the printf-style \a template_ and arguments. There can be
|
||||
* no return information, thus the query should be one which does not
|
||||
* return any. */
|
||||
void instruction (string const &template_, ...);
|
||||
|
||||
|
||||
|
||||
/** Implementation of following (\c scalar_result) method. */
|
||||
static string string_database_result (MYSQL *const mysql,
|
||||
string const &template_,
|
||||
va_list arguments);
|
||||
|
||||
/** Make a query on the database which obtains a single-valued result.
|
||||
* The \a fallback_value serves both to define the type of result
|
||||
* returned, and sets the returned value in the case that the
|
||||
* database is unable to furnish the information. The SQL query
|
||||
* itself is composed of the printf()-type string \a template_ with
|
||||
* substitutions from the following arguments. */
|
||||
template <typename T>
|
||||
T scalar_result (T const &fallback_value,
|
||||
string const &template_,
|
||||
...);
|
||||
|
||||
|
||||
/** Either make a connection to the database (\c mysql must *not*
|
||||
* already be connected), or else throw an \c Exception. */
|
||||
void connect (const Preferences&);
|
||||
|
||||
/** Close and re-make the connection to the database. */
|
||||
void reconnect (const Preferences&);
|
||||
|
||||
|
||||
} ; /* End of class DB_Connection. */
|
||||
|
||||
|
||||
|
||||
/*******************************************************************
|
||||
****************** Template implementations ********************
|
||||
*******************************************************************/
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline T Simple_Query::return_scalar (T const &fallback)
|
||||
{
|
||||
string const hold = return_scalar (string {"@@"});
|
||||
|
||||
if (hold.length () == 2 && hold == "@@") return fallback;
|
||||
|
||||
T ret; istringstream i {hold}; i >> ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<>
|
||||
inline string Simple_Query::return_scalar<string> (string const &fallback)
|
||||
{
|
||||
if (Instruction::execute () != 0) return fallback;
|
||||
|
||||
MYSQL_RES *const results = mysql_store_result (mysql);
|
||||
|
||||
if (! results) return fallback;
|
||||
|
||||
MYSQL_ROW const row = mysql_fetch_row (results);
|
||||
|
||||
string const ret = row ? string {row [0]} : fallback;
|
||||
|
||||
mysql_free_result (results);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
inline string Row_Query::next_entry<string> (string const &fallback)
|
||||
{
|
||||
auto const &ret = row [next_index++];
|
||||
return ret ? ret : fallback;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
inline Row_Query &Row_Query::operator>> <string> (string &ret)
|
||||
{
|
||||
ret = next_entry (string {});
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
inline Row_Query &Row_Query::operator>> <chrono::system_clock::time_point>
|
||||
(chrono::system_clock::time_point &ret)
|
||||
{
|
||||
time_t t;
|
||||
(*this) >> t;
|
||||
ret = chrono::system_clock::from_time_t (t);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline T DB_Connection::scalar_result (T const &fallback_value,
|
||||
string const &template_,
|
||||
...)
|
||||
{
|
||||
va_list args; va_start (args, template_);
|
||||
string const value {string_database_result (&mysql, template_, args)};
|
||||
va_end (args);
|
||||
|
||||
if (! value.length ())
|
||||
return fallback_value;
|
||||
|
||||
istringstream i (value);
|
||||
T ret;
|
||||
i >> ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <>
|
||||
inline string DB_Connection::scalar_result (const string& fallback_value,
|
||||
const string& template_,
|
||||
...)
|
||||
{
|
||||
va_list args; va_start (args, template_);
|
||||
const string value {string_database_result (&mysql, template_, args)};
|
||||
va_end (args);
|
||||
|
||||
if (! value.length ()) return fallback_value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
} } /* End of namespace DMBCS::Trader_Desk::Mysql. */
|
||||
|
||||
|
||||
#endif /* Defined DMBCS__TRADER_DESK__MYSQL__H. */
|
||||
360
trader-desk/preferences.cc
Normal file
360
trader-desk/preferences.cc
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include "preferences.h"
|
||||
#include "alpha-vantage.h"
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
|
||||
Preferences Preferences::defaults ()
|
||||
{
|
||||
return
|
||||
{
|
||||
.trade_cost_offset = 0.0,
|
||||
.trade_cost_factor = 0.0,
|
||||
.database_host = "localhost",
|
||||
.database_user = "trader_desk",
|
||||
.database_password = "123456",
|
||||
.database_instance = "trader_desk",
|
||||
.database_socket = "/run/mysqld/mysqld.sock",
|
||||
.database_port = 3306,
|
||||
.market_meta_data_service = "https://rdmp.org:9443/trader-desk/",
|
||||
.market_data_service = "https://www.alphavantage.co/query",
|
||||
.market_data_service_key = ""
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
void dump (const Preferences& P)
|
||||
{
|
||||
dump (P, P.file_path.empty ()
|
||||
? getenv ("HOME") + string {"/.config/trader-desk.conf"}
|
||||
: P.file_path);
|
||||
}
|
||||
|
||||
void dump (const Preferences& P, const string& file)
|
||||
{ dump (P, ofstream {file}); }
|
||||
|
||||
static Preferences dump (Preferences&& P, const string& file)
|
||||
{ dump (P, file); return P; }
|
||||
|
||||
void dump (const Preferences& P, ostream&& O)
|
||||
{
|
||||
O << "trade_cost_offset: " << P.trade_cost_offset << "\n"
|
||||
<< "trade_cost_factor: " << P.trade_cost_factor << "\n"
|
||||
<< "database_host: " << P.database_host << "\n"
|
||||
<< "database_user: " << P.database_user << "\n"
|
||||
<< "database_password: " << P.database_password << "\n"
|
||||
<< "database_instance: " << P.database_instance << "\n"
|
||||
<< "database_socket: " << P.database_socket << "\n"
|
||||
<< "database_port: " << P.database_port << "\n"
|
||||
<< "market_meta_data_service: " << P.market_meta_data_service << "\n"
|
||||
<< "market_data_service: " << P.market_data_service << "\n"
|
||||
<< "market_data_service_key: " << P.market_data_service_key << "\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
string Preferences::default_file_name ()
|
||||
{ return getenv ("HOME") + string {"/.config/trader-desk.conf"}; }
|
||||
|
||||
Preferences Preferences::from_default_file ()
|
||||
{ return from_file (default_file_name ()); }
|
||||
|
||||
Preferences Preferences::from_file (const string& file)
|
||||
{
|
||||
Preferences ret
|
||||
{[&file] { if (ifstream F {file}; F.good ())
|
||||
return from_file (move (F));
|
||||
return dump (defaults (), file); } () };
|
||||
ret.file_path = file;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static string read_line (istream& I)
|
||||
{
|
||||
static regex R {" *[^:\n]*: *(.*)$"};
|
||||
smatch M;
|
||||
string line;
|
||||
while (getline (I, line), I.good ())
|
||||
if (regex_match (line, M, R)) return M [1];
|
||||
return {};
|
||||
}
|
||||
|
||||
Preferences Preferences::from_file (istream&& I)
|
||||
{
|
||||
Preferences ret;
|
||||
ret.trade_cost_offset = strtod (read_line (I).data (), nullptr);
|
||||
ret.trade_cost_factor = strtod (read_line (I).data (), nullptr);
|
||||
ret.database_host = read_line (I);
|
||||
ret.database_user = read_line (I);
|
||||
ret.database_password = read_line (I);
|
||||
ret.database_instance = read_line (I);
|
||||
ret.database_socket = read_line (I);
|
||||
ret.database_port = atoi (read_line (I).data ());
|
||||
ret.market_meta_data_service = read_line (I);
|
||||
ret.market_data_service = read_line (I);
|
||||
ret.market_data_service_key = read_line (I);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool database_equal (const Preferences& A, const Preferences& B)
|
||||
{
|
||||
return A.database_host == B.database_host
|
||||
&& A.database_user == B.database_user
|
||||
&& A.database_password == B.database_password
|
||||
&& A.database_instance == B.database_instance
|
||||
&& A.database_socket == B.database_socket
|
||||
&& A.database_port == B.database_port;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void commit_prefs (Preferences_Dialog& D, Preferences& P)
|
||||
{
|
||||
/* Pounds to pence. */
|
||||
P.trade_cost_offset = D.trade_cost_offset.get_value () * 100;
|
||||
/* Percent to fraction. */
|
||||
P.trade_cost_factor = D.trade_cost_factor.get_value () / 100.0;
|
||||
|
||||
P.database_host = D.database_host.get_text ();
|
||||
P.database_user = D.database_user.get_text ();
|
||||
P.database_password = D.database_password.get_text ();
|
||||
P.database_instance = D.database_instance.get_text ();
|
||||
P.database_port = atoi (D.database_port.get_text ().data ());
|
||||
P.database_socket = D.database_socket.get_text ();
|
||||
|
||||
P.market_meta_data_service = D.market_meta_data_service.get_text ();
|
||||
P.market_data_service = D.market_data_service.get_text ();
|
||||
P.market_data_service_key = D.market_data_service_key.get_text ();
|
||||
|
||||
dump (P);
|
||||
}
|
||||
|
||||
|
||||
static void set_margins (Gtk::Widget *const W, const int size)
|
||||
{
|
||||
W->set_margin_start (size);
|
||||
W->set_margin_end (size);
|
||||
W->set_margin_top (size);
|
||||
W->set_margin_bottom (size);
|
||||
}
|
||||
|
||||
Preferences_Dialog::Preferences_Dialog
|
||||
(Gtk::Window &parent,
|
||||
Preferences& P,
|
||||
const string& alpha_vantage__message)
|
||||
: Gtk::Dialog (pgettext ("Window title", "Trader-Desk: Preferences"),
|
||||
parent),
|
||||
preferences {P}
|
||||
{
|
||||
get_vbox ()->set_spacing (20);
|
||||
|
||||
|
||||
/************* Trade costs **************************/
|
||||
|
||||
trade_cost_offset.set_increments (1.0, 10.0);
|
||||
trade_cost_offset.set_range (0.0, 1000.0);
|
||||
/* Pence to pounds. */
|
||||
trade_cost_offset.set_value (preferences.trade_cost_offset / 100);
|
||||
|
||||
trade_cost_factor.set_increments (1.0, 10.0);
|
||||
trade_cost_factor.set_range (0.0, 100.0);
|
||||
/* Fraction to percent. */
|
||||
trade_cost_factor.set_value (preferences.trade_cost_factor * 100.0);
|
||||
|
||||
{
|
||||
auto *const frame { Gtk::make_managed<Gtk::Frame>
|
||||
(pgettext ("Label", "Trading costs"))};
|
||||
get_vbox ()->pack_start (*frame, Gtk::PACK_SHRINK);
|
||||
|
||||
auto *const grid {Gtk::make_managed<Gtk::Grid> ()};
|
||||
frame->add (*grid);
|
||||
|
||||
grid->set_column_spacing (20);
|
||||
set_margins (grid, 10);
|
||||
grid->set_margin_left (100);
|
||||
|
||||
auto grid_line
|
||||
{ [grid] (const int row, const string& label,
|
||||
Gtk::Widget& control, const string& units)
|
||||
{
|
||||
grid->attach (*Gtk::make_managed<Gtk::Label> (label, Gtk::ALIGN_END),
|
||||
0, row);
|
||||
grid->attach (control, 1, row);
|
||||
grid->attach (*Gtk::make_managed<Gtk::Label>
|
||||
(units, Gtk::ALIGN_START),
|
||||
2, row);
|
||||
}
|
||||
};
|
||||
|
||||
grid_line (0, pgettext ("Label", "Fixed cost of trading"),
|
||||
trade_cost_offset,
|
||||
pgettext ("Units:Worded:Monetary", "pounds"));
|
||||
|
||||
grid_line (1, pgettext ("Label", "Proportional cost of trading"),
|
||||
trade_cost_factor,
|
||||
pgettext ("Units:Worded", "percent"));
|
||||
}
|
||||
|
||||
|
||||
Gtk::Grid *const database_ {Gtk::make_managed<Gtk::Grid> ()};
|
||||
{
|
||||
Gtk::Frame *const df {Gtk::make_managed<Gtk::Frame>
|
||||
(pgettext ("Label", "Local database"))};
|
||||
get_vbox ()->pack_start (*df, Gtk::PACK_SHRINK);
|
||||
df->add (*database_);
|
||||
database_->set_column_spacing (20);
|
||||
set_margins (database_, 10);
|
||||
database_->set_margin_left (100);
|
||||
}
|
||||
|
||||
|
||||
auto create_text_input
|
||||
{
|
||||
[this, &D = *database_]
|
||||
(const int row, const string& label, Gtk::Entry& E,
|
||||
const string& initial_value)
|
||||
{
|
||||
D.attach (*Gtk::make_managed<Gtk::Label> (label, Gtk::ALIGN_END),
|
||||
0, row);
|
||||
D.attach (E, 1, row);
|
||||
E.set_text (initial_value);
|
||||
E.set_input_purpose (Gtk::INPUT_PURPOSE_FREE_FORM);
|
||||
E.set_hexpand (1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
create_text_input (0, pgettext ("Label", "Database host"),
|
||||
database_host, preferences.database_host);
|
||||
|
||||
create_text_input (1, pgettext ("Label", "Database user"),
|
||||
database_user, preferences.database_user);
|
||||
|
||||
create_text_input (2, pgettext ("Label", "Database password"),
|
||||
database_password, preferences.database_password);
|
||||
database_password.set_input_purpose (Gtk::INPUT_PURPOSE_PASSWORD);
|
||||
database_password.set_visibility (0);
|
||||
database_password.set_invisible_char ('*');
|
||||
|
||||
create_text_input (3, pgettext ("Label", "Database instance"),
|
||||
database_instance, preferences.database_instance);
|
||||
|
||||
database_->attach (*Gtk::make_managed<Gtk::Label>
|
||||
(pgettext ("Label", "Database port"), Gtk::ALIGN_END),
|
||||
0, 4);
|
||||
database_port.set_text (to_string (preferences.database_port));
|
||||
database_port.set_input_purpose (Gtk::INPUT_PURPOSE_DIGITS);
|
||||
database_->attach (database_port, 1, 4);
|
||||
|
||||
create_text_input (5, pgettext ("Label", "Database socket"),
|
||||
database_socket, preferences.database_socket);
|
||||
|
||||
|
||||
|
||||
|
||||
Gtk::Grid *const servers_ {Gtk::make_managed<Gtk::Grid> ()};
|
||||
{
|
||||
Gtk::Frame *const F {Gtk::make_managed<Gtk::Frame>
|
||||
(pgettext ("Label", "Internet services"))};
|
||||
get_vbox ()->pack_start (*F, Gtk::PACK_SHRINK);
|
||||
F->add (*servers_);
|
||||
servers_->set_column_spacing (20);
|
||||
set_margins (servers_, 10);
|
||||
servers_->set_margin_left (100);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
servers_->attach (*Gtk::make_managed<Gtk::Label>
|
||||
(pgettext ("Label", "Market meta-data service"),
|
||||
Gtk::ALIGN_END),
|
||||
0, 0);
|
||||
market_meta_data_service.set_hexpand (1);
|
||||
market_meta_data_service.set_text (preferences.market_meta_data_service);
|
||||
market_meta_data_service.set_width_chars
|
||||
(max<int> (preferences.market_meta_data_service.length (), 40));
|
||||
servers_->attach (market_meta_data_service, 1, 0);
|
||||
|
||||
servers_->attach (*Gtk::make_managed<Gtk::Label>
|
||||
(pgettext ("Label", "Market data service"),
|
||||
Gtk::ALIGN_END),
|
||||
0, 1);
|
||||
market_data_service.set_hexpand (1);
|
||||
market_data_service.set_text (preferences.market_data_service);
|
||||
servers_->attach (market_data_service, 1, 1);
|
||||
|
||||
servers_->attach (*Gtk::make_managed<Gtk::Label>
|
||||
(pgettext ("Label", "Market data service API key"),
|
||||
Gtk::ALIGN_END),
|
||||
0, 2);
|
||||
market_data_service_key.set_hexpand (1);
|
||||
market_data_service_key.set_input_purpose (Gtk::INPUT_PURPOSE_PASSWORD);
|
||||
market_data_service_key.set_visibility (0);
|
||||
market_data_service_key.set_invisible_char ('*');
|
||||
market_data_service_key.set_text (preferences.market_data_service_key);
|
||||
servers_->attach (market_data_service_key, 1, 2);
|
||||
|
||||
if (alpha_vantage__message.length ())
|
||||
{
|
||||
Gtk::Frame *const F {Gtk::make_managed<Gtk::Frame>
|
||||
("Message from AlphaVantage service:")};
|
||||
servers_->attach (*F, 0, 3, 2, 1);
|
||||
F->set_margin_top (18);
|
||||
Gtk::TextView *const T {Gtk::make_managed<Gtk::TextView> ()};
|
||||
F->add (*T);
|
||||
T->set_wrap_mode (Gtk::WRAP_WORD);
|
||||
T->get_buffer ()
|
||||
->insert_markup (T->get_buffer ()->end (),
|
||||
"<span foreground=\"#aa0000\">"
|
||||
+ alpha_vantage__message + "</span>");
|
||||
}
|
||||
|
||||
/* The action buttons. */
|
||||
{
|
||||
Gtk::HBox *const H {Gtk::make_managed<Gtk::HBox> ()};
|
||||
get_vbox ()->pack_start (*H, Gtk::PACK_SHRINK);
|
||||
Gtk::Button *const B1 {Gtk::make_managed<Gtk::Button>
|
||||
(pgettext ("Label", "Commit changes"))};
|
||||
H->pack_start (*B1, Gtk::PACK_EXPAND_PADDING);
|
||||
B1->signal_clicked ()
|
||||
.connect ([this] { commit_prefs (*this, this->preferences);
|
||||
close (); });
|
||||
Gtk::Button *const B2 {Gtk::make_managed<Gtk::Button>
|
||||
(pgettext ("Label", "Cancel"))};
|
||||
H->pack_start (*B2, Gtk::PACK_EXPAND_PADDING);
|
||||
B2 -> signal_clicked () . connect ([this] { close (); });
|
||||
}
|
||||
|
||||
show_all ();
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
136
trader-desk/preferences.h
Normal file
136
trader-desk/preferences.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__PREFERENCES__H
|
||||
#define DMBCS__TRADER_DESK__PREFERENCES__H
|
||||
|
||||
|
||||
#include <gtkmm.h>
|
||||
#include <build-aux/gettext.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Preferences and \c Preferences_Dialog
|
||||
* classes. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/** There are a number of parameters which affect the whole of the
|
||||
* trader-desk application, and this flat structure simply contains
|
||||
* those parameters. */
|
||||
|
||||
struct Preferences
|
||||
{
|
||||
double trade_cost_offset;
|
||||
double trade_cost_factor;
|
||||
|
||||
/* MariaDB connection parameters. */
|
||||
string database_host;
|
||||
string database_user;
|
||||
string database_password;
|
||||
string database_instance;
|
||||
string database_socket;
|
||||
uint16_t database_port;
|
||||
|
||||
/* The RDMP HTTP end-point. */
|
||||
string market_meta_data_service;
|
||||
/* The AlphaVantage HTTP end-point... */
|
||||
string market_data_service;
|
||||
/* ... and private key. */
|
||||
string market_data_service_key;
|
||||
|
||||
/* Not really a user preference at this time. */
|
||||
static constexpr const int time_horizon {10};
|
||||
|
||||
/* If set, the file from whence these parameters came. */
|
||||
string file_path {};
|
||||
|
||||
static string default_file_name ();
|
||||
|
||||
static Preferences from_file (istream&&);
|
||||
static Preferences from_file (const string& file_path);
|
||||
static Preferences from_default_file ();
|
||||
|
||||
static Preferences defaults ();
|
||||
};
|
||||
|
||||
|
||||
void dump (const Preferences&, ostream&&);
|
||||
void dump (const Preferences&, const string& file_name);
|
||||
void dump (const Preferences&);
|
||||
|
||||
bool database_equal (const Preferences&, const Preferences&);
|
||||
|
||||
|
||||
|
||||
/** There are a number of parameters which affect the whole of the
|
||||
* trader-desk application, and this dialog box is designed to let the
|
||||
* user edit those global parameters from the ‘Preferences’ item on the
|
||||
* menu.
|
||||
*
|
||||
* From the applicationʼs point of view use of this object is very
|
||||
* simple: use the sole constructor to instantiate and then call the \c
|
||||
* run method. The dialog will take over all GUI operations until it
|
||||
* is closed, and in the meantime will look after itself.
|
||||
*
|
||||
* In operation the dialog will directly modify the static data in \c
|
||||
* Position_Analyzer and update the database as soon as the user makes
|
||||
* a change to any entry. */
|
||||
|
||||
struct Preferences_Dialog : Gtk::Dialog
|
||||
{
|
||||
Preferences& preferences;
|
||||
|
||||
Gtk::SpinButton trade_cost_offset {1.0, 2};
|
||||
Gtk::SpinButton trade_cost_factor {1.0, 2};
|
||||
|
||||
/* MariaDB connection parameters. */
|
||||
Gtk::Entry database_host;
|
||||
Gtk::Entry database_user;
|
||||
Gtk::Entry database_password;
|
||||
Gtk::Entry database_instance;
|
||||
Gtk::Entry database_port;
|
||||
Gtk::Entry database_socket;
|
||||
|
||||
/* The RDMP HTTP end-point. */
|
||||
Gtk::Entry market_meta_data_service;
|
||||
/* The AlphaVantage HTTP end-point... */
|
||||
Gtk::Entry market_data_service;
|
||||
/* ... and private key. */
|
||||
Gtk::Entry market_data_service_key;
|
||||
|
||||
|
||||
Preferences_Dialog (Gtk::Window &, Preferences&,
|
||||
const string& alpha_vantage_message = {});
|
||||
|
||||
|
||||
} ; /* End of class Preferences. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__PREFERENCES__H. */
|
||||
121
trader-desk/scale.cc
Normal file
121
trader-desk/scale.cc
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/scale.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Scale and \c Exponential_Scale classes. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Scale::Scale (Chart_Data &d,
|
||||
string const &label_,
|
||||
Gdk::RGBA const normal,
|
||||
Gdk::RGBA const active,
|
||||
Gdk::RGBA const prelight,
|
||||
double const min,
|
||||
double const max,
|
||||
double const initial_value)
|
||||
: data (d),
|
||||
label_format (label_),
|
||||
scale (min, max, (max-min)/100)
|
||||
{
|
||||
label.set_angle (90.0);
|
||||
|
||||
pack_start (label, Gtk::PACK_SHRINK, 0);
|
||||
pack_start (scale, Gtk::PACK_SHRINK, 0);
|
||||
|
||||
scale.get_adjustment () -> set_value (initial_value);
|
||||
|
||||
scale.set_draw_value (0);
|
||||
|
||||
scale.override_background_color (normal, Gtk::STATE_FLAG_NORMAL);
|
||||
scale.override_background_color (active, Gtk::STATE_FLAG_ACTIVE);
|
||||
scale.override_background_color (prelight, Gtk::STATE_FLAG_PRELIGHT);
|
||||
|
||||
scale . signal_value_changed ()
|
||||
. connect ([this] { on_slider_changed (); });
|
||||
|
||||
data . changed_signal
|
||||
. connect ([this] { on_data_changed (); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Scale::setup_label ()
|
||||
{
|
||||
char buffer [label_format.length () + 20];
|
||||
sprintf (buffer, label_format.c_str (), value ()->get_value ());
|
||||
label.set_text (buffer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct Exponential_Mapping
|
||||
{
|
||||
double a, b, c;
|
||||
|
||||
explicit Exponential_Mapping (Exponential_Scale const &e)
|
||||
{
|
||||
c = e.value_adjustment->get_lower ();
|
||||
|
||||
double const l = e.mid_value - c;
|
||||
double const L = e.value_adjustment->get_upper () - c;
|
||||
|
||||
a = sq (L / l - 1);
|
||||
b = l * l / (L - 2 * l);
|
||||
}
|
||||
|
||||
double to_linear (double const &x) const
|
||||
{ return log ((x - c) / b + 1) / log (a); }
|
||||
|
||||
double from_linear (double const &x) const
|
||||
{ return c + b * (pow (a, x) - 1); }
|
||||
|
||||
static constexpr double sq (double const &x) {return x*x;}
|
||||
};
|
||||
|
||||
|
||||
|
||||
void Exponential_Scale::on_slider_changed ()
|
||||
{
|
||||
value_adjustment
|
||||
->set_value (Exponential_Mapping {*this}
|
||||
.from_linear (scale.get_adjustment ()->get_value ()));
|
||||
|
||||
value_adjustment->value_changed ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Exponential_Scale::set_value (double const x)
|
||||
{
|
||||
scale.get_adjustment ()
|
||||
-> set_value (Exponential_Mapping {*this} . to_linear (x));
|
||||
|
||||
value_adjustment->set_value (x);
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
218
trader-desk/scale.h
Normal file
218
trader-desk/scale.h
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__SCALE__H
|
||||
#define DMBCS__TRADER_DESK__SCALE__H
|
||||
|
||||
|
||||
#include <trader-desk/chart-data.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of \c Scale, \c Linear_Scale, and \c Exponential_Scale
|
||||
* classes. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A \c Scale is a prototypical vertical slider control which gets
|
||||
* stacked to the right of a detail (\c Hand_Analysis_Widget) chart
|
||||
* allowing the user some control over the display of information on
|
||||
* top of that chart.
|
||||
*
|
||||
* Note that the rest of the program interacts with these objects via
|
||||
* the \c Adjustments, in particular listening for the \c value_changed
|
||||
* signals on those objects. */
|
||||
|
||||
class Scale : public Gtk::HBox
|
||||
{
|
||||
protected:
|
||||
|
||||
/** The data which the user is currently observing, which should be
|
||||
* regarded as a read-only object which may influence the operation
|
||||
* of the \c Scale slider (although the--extremely special--\c
|
||||
* Date_Range_Scale does cause changes to happen in the \c data). */
|
||||
Chart_Data &data;
|
||||
|
||||
/** A graphical component of the \c Scale widget: displays an
|
||||
* identifying text string according to the \c label_format. */
|
||||
Gtk::Label label;
|
||||
|
||||
/** A \c printf-style format string which may include a place-holder
|
||||
* for a floating-point value; this is used as the identifying label
|
||||
* on the slider and will display the slider's current value instead
|
||||
* of the placeholder. */
|
||||
string const label_format;
|
||||
|
||||
/** The actual GTK widget which implements our \c Scale. */
|
||||
Gtk::VScale scale;
|
||||
|
||||
/** Called internally whenever the user moves the slider on the
|
||||
* screen. This is the place to transform the slider's internal \c
|
||||
* Adjustment into the adjustment we present to the application. */
|
||||
virtual void on_slider_changed () { setup_label (); }
|
||||
|
||||
/** Called when an externally generated signal fires on the \c data
|
||||
* object to indicate that some change has taken place in those
|
||||
* data. */
|
||||
virtual void on_data_changed () {}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** The sole constructor, which provides a fully populated object
|
||||
* ready for operations (though often the range of values covered by
|
||||
* the slider is not known until later). */
|
||||
Scale (Chart_Data &d,
|
||||
string const &label_format,
|
||||
Gdk::RGBA const normal,
|
||||
Gdk::RGBA const active,
|
||||
Gdk::RGBA const prelight,
|
||||
double const min, double const max,
|
||||
double const initial_value = -1.0);
|
||||
|
||||
/* The underlying GTK widget machinery severely restricts any hope we
|
||||
* may have of copying and moving this type of object. */
|
||||
|
||||
/** Obligatory destructor for a pure virtual interface. */
|
||||
virtual ~Scale () = default;
|
||||
|
||||
/** Provide an adjustment. This may be the actual \c Adjustment under
|
||||
* control of the slider, but sometimes it is another object managed
|
||||
* by a derived class which apparently provides a more sophisticated
|
||||
* level of control. */
|
||||
virtual Glib::RefPtr<Gtk::Adjustment> value () = 0;
|
||||
|
||||
/* /\** Ditto above. *\/ */
|
||||
virtual Glib::RefPtr<const Gtk::Adjustment> value () const = 0;
|
||||
|
||||
/** Apply the `value' of the slider to the \c label_format string and
|
||||
* put the result as the text displayed by the \c label. */
|
||||
void setup_label ();
|
||||
|
||||
|
||||
}; /* End of class Scale. */
|
||||
|
||||
|
||||
|
||||
/** Most basic concrete \c Scale, basically a wrapper around the base
|
||||
* class which returns the slider's own (directly controlled)
|
||||
* adjustment as the value given to the user. Even though this is a
|
||||
* concrete type, it should only be used as the base of a more
|
||||
* specialized \c Scale which represents some useful quantity. */
|
||||
|
||||
struct Linear_Scale : Scale
|
||||
{
|
||||
/** Sole constructor which creates a fully populated object. */
|
||||
Linear_Scale (Chart_Data &d,
|
||||
string const &label_,
|
||||
Gdk::RGBA const &normal,
|
||||
Gdk::RGBA const &active,
|
||||
Gdk::RGBA const &prelight,
|
||||
double const min, double const max,
|
||||
double const initial_value)
|
||||
: Scale (d, label_, normal, active, prelight, min, max, initial_value)
|
||||
{
|
||||
setup_label ();
|
||||
}
|
||||
|
||||
/** Simply return the slider's directly-controlled adjustment
|
||||
* object. */
|
||||
Glib::RefPtr<Gtk::Adjustment> value () override { return scale.get_adjustment (); }
|
||||
|
||||
/* /\** Ditto. *\/ */
|
||||
Glib::RefPtr<const Gtk::Adjustment> value () const override
|
||||
{ return scale.get_adjustment (); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** A \c Scale which is exponential (or logarithmic, depending on which
|
||||
* way you like to look at things), allowing for higher values to be
|
||||
* more tightly packed together.
|
||||
*
|
||||
* The scale is parameterized at user level by the values of the
|
||||
* end-points and (linear) mid-point, and in all other respects acts
|
||||
* just like any other \C Scale object would with the exception that
|
||||
* new values are assigned through the \c set_value method; the
|
||||
* application should *not* try to set the value directly in the
|
||||
* adjustment which is given out.
|
||||
*
|
||||
* Although this is a concrete class, it is anticipated that the
|
||||
* application will only see further specializations derived from this
|
||||
* one. */
|
||||
|
||||
struct Exponential_Scale : Scale
|
||||
{
|
||||
/** The value at the linear mid-point of the scale. */
|
||||
double mid_value;
|
||||
|
||||
/** The \c Adjustment which we present to the user, exhibiting the
|
||||
* exponentially varying values. */
|
||||
Glib::RefPtr<Gtk::Adjustment> value_adjustment;
|
||||
|
||||
/** When the user moves the graphical slider. */
|
||||
void on_slider_changed () override;
|
||||
|
||||
|
||||
/** The sole constructor, which fully specifies an \c
|
||||
* Exponential_Scale object. */
|
||||
Exponential_Scale (Chart_Data &d,
|
||||
string const &label,
|
||||
Gdk::RGBA const normal,
|
||||
Gdk::RGBA const active,
|
||||
Gdk::RGBA const prelight,
|
||||
double const lower, double const mid, double const upper,
|
||||
double const initial_value)
|
||||
: Scale (d, label, normal, active, prelight, 0.0, 1.0),
|
||||
mid_value (mid),
|
||||
value_adjustment {Gtk::Adjustment::create (mid, lower, upper)}
|
||||
{
|
||||
value_adjustment -> signal_value_changed ()
|
||||
. connect ([this] { setup_label (); });
|
||||
|
||||
set_value (initial_value);
|
||||
}
|
||||
|
||||
|
||||
/** The means by which the application imposes a value on the
|
||||
* slider. */
|
||||
void set_value (double const x);
|
||||
|
||||
|
||||
/** Give the logical, i.e. not the linear, value to the user. */
|
||||
Glib::RefPtr<Gtk::Adjustment> value () override { return value_adjustment; }
|
||||
|
||||
|
||||
/* /\** Ditto, but for \c const objects. *\/ */
|
||||
Glib::RefPtr<const Gtk::Adjustment> value () const override
|
||||
{ return value_adjustment; }
|
||||
|
||||
|
||||
}; /* End of class Exponential_Scale. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__SCALE__H. */
|
||||
174
trader-desk/sd-envelope-analyzer.cc
Normal file
174
trader-desk/sd-envelope-analyzer.cc
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <numeric>
|
||||
#include <trader-desk/sd-envelope-analyzer.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c SD_Envelope_Analyzer class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
SD_Envelope_Analyzer::SD_Envelope_Analyzer (Chart_Data &cd)
|
||||
: moving_average {cd}
|
||||
{
|
||||
moving_average . signal_redraw_needed ()
|
||||
. connect ([this] { data_changed (); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
vector <Gtk::Widget*> SD_Envelope_Analyzer::make_control_widgets ()
|
||||
{
|
||||
vector <Gtk::Widget*> ret = moving_average.make_control_widgets ();
|
||||
|
||||
Scale *const s = new Scale {moving_average.chart_data,
|
||||
envelope_width};
|
||||
|
||||
s -> value () -> signal_value_changed ()
|
||||
. connect ([this, s] { control_moved (s); } );
|
||||
|
||||
ret.emplace_back (s);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename X> inline X sq (X const &x) { return x*x; }
|
||||
|
||||
|
||||
static Currency_Value standard_deviation_ (Time_Series const &t,
|
||||
Time_Series const &mean,
|
||||
Time_Point const &earliest_time)
|
||||
{
|
||||
if (mean.size () < 2)
|
||||
return 0.0;
|
||||
|
||||
auto const end_ = std::upper_bound
|
||||
(begin (mean), end (mean),
|
||||
earliest_time,
|
||||
[] (Time_Point const &val,
|
||||
Time_Series::value_type const &a)
|
||||
{ return a.time < val; });
|
||||
|
||||
return std::sqrt (std::inner_product (begin (mean), end_,
|
||||
begin (t),
|
||||
Currency_Value {0.0},
|
||||
plus<Currency_Value> {},
|
||||
[] (Event const &a, Event const &b)
|
||||
{ return sq (a.price - b.price); })
|
||||
/ (mean.size () - 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SD_Envelope_Analyzer::data_changed ()
|
||||
{
|
||||
{
|
||||
lock_guard<mutex> l {moving_average.chart_data.prices_mutex};
|
||||
|
||||
standard_deviation
|
||||
= standard_deviation_ (moving_average.chart_data.prices,
|
||||
moving_average.mean_series,
|
||||
moving_average.chart_data.extremes.start_time);
|
||||
}
|
||||
|
||||
redraw_needed_.emit ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SD_Envelope_Analyzer::control_moved (Scale const *const scale)
|
||||
{
|
||||
if (abs (envelope_width - scale->value ()->get_value ()) > 1.0e-3)
|
||||
{
|
||||
envelope_width = scale->value ()->get_value ();
|
||||
redraw_needed_.emit ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SD_Envelope_Analyzer::stretch_outline (Time_Series::Range &outline)
|
||||
{
|
||||
auto const range = moving_average.mean_series.get_range ();
|
||||
auto const margin = (outline.max_value - outline.min_value) * 0.05;
|
||||
|
||||
outline.max_value = max (outline.max_value,
|
||||
range.max_value
|
||||
+ envelope_width * standard_deviation
|
||||
+ margin);
|
||||
|
||||
outline.min_value = min (outline.min_value,
|
||||
range.min_value
|
||||
- envelope_width * standard_deviation
|
||||
- margin);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void SD_Envelope_Analyzer::graph_draw_hook
|
||||
(Chart_Context &context,
|
||||
Tide_Mark::List &marks,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &markers)
|
||||
{
|
||||
if (moving_average.mean_series.empty ())
|
||||
return;
|
||||
|
||||
context.set_source_rgb (Colour::SD_ENVELOPE);
|
||||
|
||||
context.move_to (moving_average.mean_series.front ());
|
||||
|
||||
auto const envelope = envelope_width * standard_deviation;
|
||||
|
||||
auto i = begin (moving_average.mean_series);
|
||||
|
||||
for (;
|
||||
i != end (moving_average.mean_series)
|
||||
&& i->time >= context.outline.start_time;
|
||||
++i)
|
||||
context.line_to ({i->time, i->price + envelope});
|
||||
|
||||
for (--i; i >= begin (moving_average.mean_series); --i)
|
||||
context.line_to ({i->time, i->price - envelope});
|
||||
|
||||
context.cairo->fill ();
|
||||
|
||||
for (auto const &t : markers)
|
||||
{
|
||||
auto const mean
|
||||
= moving_average.mean_series
|
||||
.interpolated_value (t (0.0, Colour::MEAN_TIDE).time);
|
||||
|
||||
marks.emplace_back (t (mean - envelope, Colour::ENVELOPE_TIDES));
|
||||
marks.emplace_back (t (mean + envelope, Colour::ENVELOPE_TIDES));
|
||||
}
|
||||
|
||||
moving_average . graph_draw_hook (context, marks, number_shares, markers);
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
127
trader-desk/sd-envelope-analyzer.h
Normal file
127
trader-desk/sd-envelope-analyzer.h
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__SD_ENVELOPE_ANALYZER__H
|
||||
#define DMBCS__TRADER_DESK__SD_ENVELOPE_ANALYZER__H
|
||||
|
||||
|
||||
#include <trader-desk/moving-average-analyzer.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c SD_Envelope_Analyzer class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A chart analyzer which puts an envelope around the prices chart
|
||||
* related to the standard deviation (SD) of the prices data around
|
||||
* their mean: any proportion of this SD can be chosen by the user
|
||||
* acting on a slider (\c Scale).
|
||||
*
|
||||
* This analyzer incorporates a \c Moving_Average_Analyzer within it,
|
||||
* and provides a composite control to the application which allows for
|
||||
* user adjustment of both the averaging window and relative width (as
|
||||
* proportion of SD) of the envelope. */
|
||||
|
||||
class SD_Envelope_Analyzer : public Analyzer
|
||||
{
|
||||
|
||||
/** Simple linear scale which allows the user to choose an envelope
|
||||
* width relative to SD between 0.1 and 10.0. */
|
||||
|
||||
struct Scale : Linear_Scale
|
||||
{
|
||||
Scale (Chart_Data &chart_data, double const &initial_value)
|
||||
: Linear_Scale (chart_data,
|
||||
pgettext ("Label Abbrev:Standard deviation",
|
||||
"Envelope width = %.2f x std. dev."),
|
||||
Gdk::RGBA {"#dddd00"},
|
||||
Gdk::RGBA {"#ffffaa"},
|
||||
Gdk::RGBA {"#ffff66"},
|
||||
0.1, 10.0, initial_value)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
/** The moving average object which we encapsulate (and leverage for
|
||||
* our own functioning). */
|
||||
Moving_Average_Analyzer moving_average;
|
||||
|
||||
/** The width of the envelope as a fraction of the \c
|
||||
* standard_deviation. */
|
||||
double envelope_width {2};
|
||||
|
||||
/** The current standard deviation of the data in the prices
|
||||
* time-series about the current mean time-series. */
|
||||
double standard_deviation {0};
|
||||
|
||||
/** We emit this signal whenever we re-compute the \c
|
||||
* standard_deviation. */
|
||||
sigc::signal<void> redraw_needed_;
|
||||
|
||||
|
||||
/** Called when the \a scale slider indicates that the user has
|
||||
* changed the position. */
|
||||
void control_moved (Scale const *const);
|
||||
|
||||
/** Called when the \c moving_average object signals that the mean
|
||||
* time-series has just been re-computed. */
|
||||
void data_changed ();
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** Sole constructor which sets up a fully populated and operational
|
||||
* object. */
|
||||
explicit SD_Envelope_Analyzer (Chart_Data &cd);
|
||||
|
||||
|
||||
/* Analyzer interface. */
|
||||
|
||||
/** Return a (newly allocated) composite object which includes sliders
|
||||
* for ourself and the \c moving_average. */
|
||||
vector <Gtk::Widget*> make_control_widgets () override;
|
||||
|
||||
/** Make sure the vertical (price) limits of the \a Range are wide
|
||||
* enough to display the full envelope. */
|
||||
void stretch_outline (Time_Series::Range &) override;
|
||||
|
||||
/** Display the envelope and \c moving_average chart. */
|
||||
void graph_draw_hook (Chart_Context &context,
|
||||
Tide_Mark::List &,
|
||||
unsigned number_shares,
|
||||
vector <Tide_Mark::Price_Marker> const &) override;
|
||||
|
||||
/** Return the signal object on which we emit when anything
|
||||
* changes. */
|
||||
sigc::signal<void> &signal_redraw_needed () override
|
||||
{ return redraw_needed_; }
|
||||
|
||||
|
||||
}; /* End of class SD_Envelope_Analyzer. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__SD_ENVELOPE_ANALYZER__H. */
|
||||
73
trader-desk/shares-scale.cc
Normal file
73
trader-desk/shares-scale.cc
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/shares-scale.h>
|
||||
#include <iomanip>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Shares_Scale::Shares_Scale (Chart_Data &d)
|
||||
: Exponential_Scale (d,
|
||||
pgettext ("Label",
|
||||
"Number of shares = %.0f"),
|
||||
Gdk::RGBA ("#00dd00"),
|
||||
Gdk::RGBA ("#aaffaa"),
|
||||
Gdk::RGBA ("#66ff66"),
|
||||
1, 1000, 10000,
|
||||
1)
|
||||
{
|
||||
d.changed_signal . connect ([this] { on_data_changed (); });
|
||||
|
||||
value_adjustment -> signal_value_changed ()
|
||||
. connect ([this] { on_value_changed (); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Shares_Scale::on_data_changed ()
|
||||
{
|
||||
if (data.open_position.price < 0)
|
||||
scale.show ();
|
||||
else
|
||||
scale.hide ();
|
||||
|
||||
if (data.number_shares != unsigned (value ()->get_value ()))
|
||||
{
|
||||
set_value (data.number_shares);
|
||||
scale.get_adjustment () -> value_changed ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Shares_Scale::on_value_changed ()
|
||||
{
|
||||
if (data.number_shares != (unsigned) (value ()->get_value () + 0.5))
|
||||
{
|
||||
data.number_shares = (unsigned) (value ()->get_value () + 0.5);
|
||||
data.update_extreme_prices ();
|
||||
data.changed_signal.emit ();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
61
trader-desk/shares-scale.h
Normal file
61
trader-desk/shares-scale.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__SHARES_SCALE__H
|
||||
#define DMBCS__TRADER_DESK__SHARES_SCALE__H
|
||||
|
||||
|
||||
#include <trader-desk/scale.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Shares_Scale class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Basic example of an \c Exponential_Scale which sets itself according
|
||||
* to the \c Chart_Data, and conversely applies user input from the
|
||||
* graphical slider to the \c Chart_Data. */
|
||||
|
||||
class Shares_Scale : public Exponential_Scale
|
||||
{
|
||||
/** Called when the user pushes the slider about. */
|
||||
void on_value_changed ();
|
||||
|
||||
/** Called when the \c Chart_Data change. */
|
||||
void on_data_changed () override;
|
||||
|
||||
public:
|
||||
|
||||
/** Sole constructor which registers the \a Chart_Data which we watch
|
||||
* over. */
|
||||
Shares_Scale (Chart_Data &d);
|
||||
|
||||
|
||||
}; /* End of class Shares_Scale. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__SHARES_SCALE__H. */
|
||||
193
trader-desk/text.cc
Normal file
193
trader-desk/text.cc
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/text.h>
|
||||
#include <limits>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
inline double clamp (double const &x, double const &l, double const &h)
|
||||
{
|
||||
return max (min (x, h), l);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static double boundary_stress (Text::Item const &i,
|
||||
Text::Boundary const &b,
|
||||
double const &critical)
|
||||
{
|
||||
double const ret
|
||||
= i.size.y * (clamp (i.placement.x + i.size.x - b.right,
|
||||
0.0, i.size.x)
|
||||
+ clamp (b.left - i.placement.x, 0.0, i.size.x))
|
||||
+ i.size.x * (clamp (b.top - i.placement.y, 0.0, i.size.y)
|
||||
+ clamp (i.placement.y + i.size.y - b.bottom,
|
||||
0.0, i.size.y));
|
||||
|
||||
return ret > -numeric_limits<double>::min () ? 2 * ret + critical
|
||||
: 0.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static float overlap (Text::Item const &a, Text::Item const &b,
|
||||
float const &critical_value)
|
||||
{
|
||||
static double const pad = 6.0;
|
||||
|
||||
if (a.placement.x >= b.placement.x + b.size.x + pad
|
||||
|| a.placement.x + a.size.x <= b.placement.x - pad
|
||||
|| a.placement.y >= b.placement.y + b.size.y
|
||||
|| a.placement.y + a.size.y <= b.placement.y)
|
||||
return 0;
|
||||
|
||||
auto const right = min (a.placement.x + a.size.x,
|
||||
b.placement.x + b.size.x) + pad;
|
||||
auto const left = max (a.placement.x, b.placement.x) - pad;
|
||||
auto const top = max (a.placement.y, b.placement.y);
|
||||
auto const bottom = min (a.placement.y + a.size.y,
|
||||
b.placement.y + b.size.y);
|
||||
|
||||
return critical_value + sqrt ((right - left) * (bottom - top));
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline double squash (double const &x)
|
||||
{
|
||||
return 1.0 - exp (-x / 100.0);
|
||||
}
|
||||
|
||||
|
||||
inline double tension (Text::Item const &i)
|
||||
{
|
||||
return squash (hypot (i.native_position.x - i.placement.x,
|
||||
i.native_position.y - i.placement.y));
|
||||
}
|
||||
|
||||
|
||||
inline void bring_inside (Text::Item &i, Text::Boundary const &b)
|
||||
{
|
||||
i.placement = {clamp (i.placement.x, b.left, b.right - i.size.x),
|
||||
clamp (i.placement.y, b.top, b.bottom - i.size.y)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
double Text::compute_tension (double const &critical_value,
|
||||
Boundary const &boundary) const
|
||||
{
|
||||
double acc {0.0};
|
||||
|
||||
for (auto a = begin (items); a != end (items); ++a)
|
||||
{
|
||||
for (auto b = a + 1; b != end (items); ++b)
|
||||
acc += overlap (*a, *b, critical_value);
|
||||
|
||||
acc += tension (*a) + boundary_stress (*a, boundary, critical_value);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Text::shuffle (double shift_size,
|
||||
double const &shift_limit,
|
||||
Boundary const &boundary)
|
||||
{
|
||||
if (items.empty ()) return;
|
||||
|
||||
for (; shift_size > shift_limit; shift_size /= 2.0)
|
||||
{
|
||||
for (auto &i : items)
|
||||
bring_inside (i, boundary);
|
||||
|
||||
auto const acceptable_limit = items.size ();
|
||||
auto initial_tension = compute_tension (acceptable_limit, boundary);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Item *hot_item {nullptr};
|
||||
enum { up, down, left, right} best_direction {up};
|
||||
double best_tension = numeric_limits<double>::max ();
|
||||
|
||||
{
|
||||
auto test = [this, acceptable_limit, &best_tension,
|
||||
&hot_item, &best_direction, boundary]
|
||||
(Item &i,
|
||||
Place const &shift,
|
||||
decltype (best_direction) const &direction)
|
||||
{
|
||||
i.placement += shift;
|
||||
auto const tension = compute_tension (acceptable_limit,
|
||||
boundary);
|
||||
if (tension < best_tension)
|
||||
{
|
||||
hot_item = &i;
|
||||
best_direction = direction;
|
||||
best_tension = tension;
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &i : items)
|
||||
{
|
||||
test (i, Place {shift_size, 0}, right);
|
||||
test (i, Place {- 2 * shift_size, 0}, left );
|
||||
test (i, Place {shift_size, shift_size}, up );
|
||||
test (i, Place {0, - 2 * shift_size}, down );
|
||||
|
||||
i.placement += Place {0, shift_size};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (! hot_item) return;
|
||||
|
||||
|
||||
switch (best_direction)
|
||||
{
|
||||
case up: hot_item->placement += {0, shift_size}; break;
|
||||
case down: hot_item->placement += {0, -shift_size}; break;
|
||||
case left: hot_item->placement += {-shift_size, 0}; break;
|
||||
case right: hot_item->placement += {shift_size, 0}; break;
|
||||
}
|
||||
|
||||
/* Has the tension decreased? If not we should give up,
|
||||
* regardless of the acceptable limit. */
|
||||
|
||||
if (initial_tension - best_tension
|
||||
< numeric_limits<decltype(initial_tension)>::epsilon ())
|
||||
break;
|
||||
|
||||
if ((initial_tension = best_tension) < acceptable_limit)
|
||||
break;
|
||||
|
||||
} /* Loop back for further improvements. */
|
||||
|
||||
} /* End of loop over shift_size. */
|
||||
|
||||
} /* End of ‘shuffle’ function. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
167
trader-desk/text.h
Normal file
167
trader-desk/text.h
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__TEXT__H
|
||||
#define DMBCS__TRADER_DESK__TEXT__H
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <trader-desk/colour.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Text class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** The primary purpose of this class is to arrange short items of
|
||||
* text in a rectangle of the screen in a way that avoids overlaps
|
||||
* and keeps the items as close to their intended positions as
|
||||
* possible.
|
||||
*
|
||||
* Thus the public interface is nice and simple: a method to add text
|
||||
* items, \c operator+=, and a method to cause those items to be
|
||||
* arranged optimally, \c arrange.
|
||||
*
|
||||
* The strategy is to move all items so that they fall inside the
|
||||
* screen rectangle, and then for increasingly small deltas determine
|
||||
* which movement, in any of the four cardinal directions, of which
|
||||
* item leads to the best improvement in score: the total amount of
|
||||
* area overlap of all the items plus a small number times the sum of
|
||||
* the itemsʼ displacement from their starting values--we want to get
|
||||
* rid of the overlaps at the expense of the displacements. The
|
||||
* exercise continues until the overlaps are eliminated and delta is
|
||||
* not more than the size of a pixel. */
|
||||
|
||||
struct Text
|
||||
{
|
||||
/** A structure to carry the details of a rectangular region on the
|
||||
* screen. */
|
||||
struct Boundary { double left, right, top, bottom; };
|
||||
|
||||
|
||||
/** A structure to carry coordinates of a point on the screen, with
|
||||
* some basic arithmetic where it serves our needs in the
|
||||
* implementation of the \c Text class. */
|
||||
struct Place
|
||||
{
|
||||
double x {0.0};
|
||||
double y {0.0};
|
||||
|
||||
Place () = default;
|
||||
|
||||
Place (double const &x_, double const &y_) : x (x_), y (y_) {}
|
||||
|
||||
Place &operator+= (Place const &a)
|
||||
{ x += a.x; y += a.y; return *this; }
|
||||
|
||||
Place &operator-= (Place const &a)
|
||||
{ x -= a.x; y -= a.y; return *this; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** An item of text to be placed on the screen. */
|
||||
|
||||
struct Item
|
||||
{
|
||||
/** The actual text string. */
|
||||
string text;
|
||||
|
||||
/** The colour in which the text should be rendered (not actually
|
||||
* needed in this class, but we carry the information along for
|
||||
* the convenience of the application). */
|
||||
Colour colour;
|
||||
|
||||
/** The place where we would like the text to appear. */
|
||||
Place native_position;
|
||||
|
||||
/** The size of the box which encloses the text. */
|
||||
Place size;
|
||||
|
||||
/** The computed place on the screen where the text should be
|
||||
* printed so that it does not overlap with any other of the
|
||||
* items. */
|
||||
Place placement;
|
||||
|
||||
|
||||
/** Sole class constructor creates a fully populated object,
|
||||
* except for the \c placement member which is set by subsequent
|
||||
* optimization of a set of objects relative to each other. */
|
||||
Item (string const &t, Colour const &c, Place const &p,
|
||||
Place const &s)
|
||||
: text {t}, colour {c}, native_position {p}, size {s},
|
||||
placement {p}
|
||||
{}
|
||||
|
||||
}; /* End of class Item. */
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/* The actual collection of text items we accumulate and
|
||||
* arrange. */
|
||||
vector<Item> items;
|
||||
|
||||
|
||||
/* Compute a score, to be minimized, for the current arrangement of
|
||||
* text \c Item \C placements. */
|
||||
double compute_tension (double const &critical_value,
|
||||
Boundary const &boundary) const;
|
||||
|
||||
|
||||
/* Randomly move some items of text around. */
|
||||
void shuffle (double shift_size,
|
||||
double const &shift_limit,
|
||||
Boundary const &boundary);
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** Return a vector of items whose \c placement's indicate the
|
||||
* optimal positions to render each text item. */
|
||||
vector<Item> const &arrange (Boundary const &b)
|
||||
{
|
||||
for (auto &i : items) i.placement = i.native_position;
|
||||
shuffle (10.0, 0.5, b);
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
/** Add a new text item to the list of items that need to be
|
||||
* optimally arranged. */
|
||||
Text &operator+= (Item &&d) { items.emplace_back (move (d));
|
||||
return *this; }
|
||||
|
||||
|
||||
} ; /* End of class Text. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__TEXT__H. */
|
||||
112
trader-desk/tide-mark.h
Normal file
112
trader-desk/tide-mark.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__TIDE_MARK__H
|
||||
#define DMBCS__TRADER_DESK__TIDE_MARK__H
|
||||
|
||||
|
||||
#include <trader-desk/colour.h>
|
||||
#include <trader-desk/time-series.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Definition and complete inline implementation of the \c Tide_Mark
|
||||
* class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A \c Tide_Mark is simply a point in (time, price)-space (i.e. an \c
|
||||
* Event) through which we draw horizontal and vertical cross-hairs.
|
||||
* The main requirement is to be able to pass a list of interesting
|
||||
* points in time around the program--the current mouse position,
|
||||
* current time, position-entry time--and then have each sub-system
|
||||
* furnish a set of price marks; this is the purpose of the \c
|
||||
* Price_Marker function type which is an object with time bound and
|
||||
* which takes a price as an argument, with the end product being an
|
||||
* actual \c Tide_Mark at that (time, price) point.
|
||||
*
|
||||
* Note further how the horizontal and vertical lines have distinctive
|
||||
* colours. */
|
||||
|
||||
struct Tide_Mark : Event
|
||||
{
|
||||
/** The container type we (the application) use for containers of \c
|
||||
* Tide_Mark's. */
|
||||
typedef vector<Tide_Mark> List;
|
||||
|
||||
/** The type of a function which manufactures a \c Tide_Mark using
|
||||
* intrinsically predefined values for the time and time-tide
|
||||
* colour. */
|
||||
typedef function <Tide_Mark (Currency_Value const &, Colour const &)>
|
||||
Price_Marker;
|
||||
|
||||
/** Colour of price-wise tide-line. */
|
||||
Colour value_colour;
|
||||
|
||||
/** Colour of time-wise tide-line. */
|
||||
Colour temporal_colour;
|
||||
|
||||
|
||||
private:
|
||||
|
||||
/* Construction must be done via the \c tide_mark function below.
|
||||
*
|
||||
* Rather than constructing these objects in one go, applications
|
||||
* must first use \c price_marker() to manufacture us a function,
|
||||
* which in turn manufactures a \c Tide_Mark. */
|
||||
constexpr Tide_Mark (Event const &e, Colour const &v, Colour const &t)
|
||||
: Event {e}, value_colour {v}, temporal_colour {t}
|
||||
{}
|
||||
|
||||
|
||||
/* No reason to make copies, so guard against accidentally doing
|
||||
* so. */
|
||||
Tide_Mark (Tide_Mark const &) = delete;
|
||||
Tide_Mark &operator= (Tide_Mark const &) = delete;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/* Moves are necessary as these items are stored in \c vector's. */
|
||||
Tide_Mark (Tide_Mark &&) = default;
|
||||
Tide_Mark &operator= (Tide_Mark &&) = default;
|
||||
|
||||
|
||||
/** A factory method which produces a \c Price_Marker, which in turn
|
||||
* produces \c Tide_Marks with event time and time-tide colours fixed
|
||||
* at \a time and \a time_colour, respectively. */
|
||||
static Price_Marker price_marker (Time_Point const &time,
|
||||
Colour const &time_colour)
|
||||
{
|
||||
return [time, time_colour] (Currency_Value const &v, Colour const &c)
|
||||
{ return Tide_Mark {{time, v}, c, time_colour}; };
|
||||
}
|
||||
|
||||
|
||||
} ; /* End of class Tide_Mark. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__TIDE_MARK__H. */
|
||||
294
trader-desk/time-series.cc
Normal file
294
trader-desk/time-series.cc
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/time-series.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Time_Series class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Time_Point const TODAY_MARK
|
||||
{ [] {
|
||||
auto round_days = [] (time_t const &t)
|
||||
{
|
||||
struct tm tm; localtime_r (&t, &tm);
|
||||
|
||||
tm.tm_sec = 0;
|
||||
tm.tm_min = 0;
|
||||
tm.tm_hour = 0;
|
||||
|
||||
return mktime (&tm);
|
||||
};
|
||||
|
||||
return chrono::system_clock::from_time_t
|
||||
(round_days
|
||||
(chrono::system_clock::to_time_t
|
||||
(chrono::system_clock::now ())));
|
||||
} ()
|
||||
};
|
||||
|
||||
|
||||
|
||||
inline Duration::rep T (Time_Point const &t)
|
||||
{
|
||||
return number<chrono::seconds> (t.time_since_epoch ());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Time_Series::insert_event (Event const &e)
|
||||
{
|
||||
const auto t {find_if (begin (), end (), [t = e.time] (const Event& a)
|
||||
{ return a.time < t; })};
|
||||
|
||||
if (t == end ()) emplace_back (e);
|
||||
else insert (t, e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Time_Series::extend_range (DB &db,
|
||||
int const seqid,
|
||||
Duration const &window_size)
|
||||
{
|
||||
auto const earliest_date = (empty () ? TODAY_MARK : front ().time)
|
||||
- window_size;
|
||||
|
||||
auto const last_time = empty () ? TODAY_MARK
|
||||
: back ().time - chrono::seconds (1);
|
||||
|
||||
if (earliest_date <= last_time)
|
||||
{
|
||||
auto const x = from_database (db,
|
||||
seqid,
|
||||
last_time,
|
||||
last_time - earliest_date,
|
||||
market_close_time);
|
||||
|
||||
insert (end (), std::begin (x), std::end (x));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Time_Series Time_Series::from_database (DB &db,
|
||||
int const seqid,
|
||||
Time_Point const &latest_date,
|
||||
Duration const &window_size,
|
||||
Duration const &market_close_time)
|
||||
{
|
||||
Time_Series ret {market_close_time};
|
||||
|
||||
|
||||
/* If the user has entered a recent price for this stock, we need to
|
||||
* splice the value into the time-series we produce (presumably we are
|
||||
* only interested in doing this if the date is later than any data we
|
||||
* already have). */
|
||||
|
||||
time_t user_date {0};
|
||||
Currency_Value user_price {0.0};
|
||||
|
||||
{
|
||||
auto sql = db.row_query ();
|
||||
|
||||
sql << "select UNIX_TIMESTAMP(last_price_date), last_price "
|
||||
<< " from company "
|
||||
<< " where company.seqid=" << seqid;
|
||||
|
||||
sql.execute ();
|
||||
|
||||
if (sql) {
|
||||
user_date = sql.next_entry (user_date);
|
||||
user_price = sql.next_entry (user_price);
|
||||
++sql;
|
||||
}
|
||||
}
|
||||
|
||||
if (user_date > T (latest_date)) user_date = 0;
|
||||
|
||||
{
|
||||
auto sql = db.row_query ();
|
||||
|
||||
sql << " select unix_timestamp(date), close "
|
||||
<< " from prices "
|
||||
<< " where company=" << seqid
|
||||
<< " and date >= from_unixtime("
|
||||
<< T (latest_date - window_size) << ") "
|
||||
<< " and date <= from_unixtime(" << T (latest_date) << ") "
|
||||
<< "order by date desc";
|
||||
|
||||
sql.execute ();
|
||||
|
||||
if (! sql) return ret;
|
||||
|
||||
auto user_time = chrono::system_clock::from_time_t (user_date);
|
||||
|
||||
for (; sql; ++sql)
|
||||
{
|
||||
auto const date = sql.next_entry<Time_Point> ()
|
||||
+ ret.market_close_time;
|
||||
|
||||
if (user_time > date)
|
||||
{
|
||||
ret.emplace_back (user_date, user_price);
|
||||
user_time = chrono::system_clock::from_time_t (0);
|
||||
}
|
||||
|
||||
ret.emplace_back (date, sql.next_entry (Currency_Value {0.0}));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
} /* End of from_database method. */
|
||||
|
||||
|
||||
|
||||
auto Time_Series::interpolated_value (Time_Point const &date) const
|
||||
-> Currency_Value
|
||||
{
|
||||
if (empty ())
|
||||
return 0;
|
||||
|
||||
auto const t = lower_bound (rbegin (), rend (),
|
||||
Event {date, 0.0},
|
||||
[] (value_type const &a,
|
||||
value_type const &b)
|
||||
{ return a.time < b.time; });
|
||||
|
||||
if (t == rbegin ())
|
||||
return t->price;
|
||||
|
||||
return (t-1)->price + (t->price - (t-1)->price)
|
||||
* ((T (date) - T ((t-1)->time))
|
||||
/ (double) (T (t->time) - T ((t-1)->time)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
auto Time_Series::get_range (Duration const &date_range) const -> Range
|
||||
{
|
||||
Range ret;
|
||||
|
||||
if (empty ())
|
||||
return ret;
|
||||
|
||||
ret.end_time = front ().time;
|
||||
|
||||
const_iterator const end_
|
||||
= date_range == chrono::seconds::max ()
|
||||
? end ()
|
||||
: std::upper_bound (begin (),
|
||||
end (),
|
||||
ret.end_time - date_range,
|
||||
[] (Time_Point const &val, value_type const &a)
|
||||
{ return a.time < val; });
|
||||
|
||||
auto const e
|
||||
= minmax_element (begin (),
|
||||
end_,
|
||||
[] (value_type const &a, value_type const &b)
|
||||
{ return a.price < b.price; });
|
||||
|
||||
ret.min_value = e.first->price;
|
||||
ret.max_value = e.second->price;
|
||||
|
||||
if (end_ != begin ()) ret.start_time = (end_ - 1)->time;
|
||||
else ret.start_time = end_->time;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Time_Series Time_Series::compute_moving_average (Time_Series const &in,
|
||||
Duration const &window,
|
||||
Time_Point const &earliest)
|
||||
{
|
||||
if (in.size () < 2)
|
||||
return in;
|
||||
|
||||
Time_Series ret {in.market_close_time};
|
||||
|
||||
auto const forward_window_size = window / 2;
|
||||
auto const backward_window_size = window - forward_window_size;
|
||||
|
||||
auto const start_time = earliest - window;
|
||||
|
||||
/* These iterators run through _increasing_ dates. */
|
||||
auto window_front = in.rbegin ();
|
||||
while (window_front->time < start_time && window_front != in.rend ())
|
||||
++window_front;
|
||||
auto window_back = window_front;
|
||||
|
||||
int count = 0;
|
||||
double sum = 0.0;
|
||||
|
||||
for (;
|
||||
window_front != in.rend ()
|
||||
&& window_front->time < window_back->time + forward_window_size;
|
||||
++ window_front)
|
||||
{
|
||||
sum += window_front->price;
|
||||
++count;
|
||||
}
|
||||
|
||||
ret.emplace_back (window_back->time,
|
||||
count > 0 ? sum / count : 0.0);
|
||||
|
||||
for (auto i = window_back + 1; i != in.rend (); ++i)
|
||||
{
|
||||
for (;
|
||||
window_front != in.rend ()
|
||||
&& window_front->time < i->time + forward_window_size;
|
||||
++window_front)
|
||||
{
|
||||
sum += window_front->price;
|
||||
++ count;
|
||||
}
|
||||
|
||||
for (;
|
||||
window_back != window_front
|
||||
&& window_back->time < i->time - backward_window_size;
|
||||
++window_back)
|
||||
{
|
||||
sum -= window_back->price;
|
||||
-- count;
|
||||
}
|
||||
|
||||
ret.emplace_back (i->time, count > 0 ? sum / count : 0.0);
|
||||
}
|
||||
|
||||
reverse (std::begin (ret), std::end (ret));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
208
trader-desk/time-series.h
Normal file
208
trader-desk/time-series.h
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__TIME_SERIES__H
|
||||
#define DMBCS__TRADER_DESK__TIME_SERIES__H
|
||||
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
#include <trader-desk/db.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Definition of \c Time_Point, Currency_Value, Duration types and the
|
||||
* composed \c Event type, declaration of \c Time_Series class, and
|
||||
* definition of convenience functions \c number<tick> (count ticks in
|
||||
* duration) and \c t (construct a \c time_t from real-world calendar
|
||||
* data). */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** All trades and closing prices are fixed to a \c Time_Point. */
|
||||
typedef chrono::system_clock::time_point Time_Point;
|
||||
|
||||
/** All trades and closing prices are expressed as \c
|
||||
* Currency_Value's. */
|
||||
typedef double Currency_Value;
|
||||
|
||||
/** All time spans are expressed as \c Duration's. */
|
||||
typedef Time_Point::duration Duration;
|
||||
|
||||
|
||||
/* The time at which the day started when this application started
|
||||
* running. Whenever we consider closing prices, we regard this as the
|
||||
* high-tide mark of the window of closing events. */
|
||||
/* !!! We really need to re-evaluate the need for this. */
|
||||
extern const Time_Point TODAY_MARK;
|
||||
|
||||
|
||||
/** Whatever flavour of \c DURATION \a t we have, determine the number
|
||||
* of complete \c TICK's that the duration spans. */
|
||||
template <typename TICK, typename DURATION>
|
||||
inline constexpr Duration::rep number (DURATION const &t)
|
||||
{
|
||||
return chrono::duration_cast<TICK> (t) . count ();
|
||||
}
|
||||
|
||||
|
||||
/** Convert from the ‘normal’ notion of calendar date to a \c time_t.
|
||||
* THE ARGUMENTS ARE STREET-WISE VALUES. */
|
||||
inline time_t t (int const year, int const month, int const day,
|
||||
int const hour, int const minute, int const second)
|
||||
{
|
||||
struct tm t {second, minute, hour, day, month - 1, year - 1900,
|
||||
0, 0, 0, 0, 0};
|
||||
return mktime (&t);
|
||||
}
|
||||
|
||||
/** Convert from the ‘normal’ notion of calendar date to a \c time_t.
|
||||
* THE ARGUMENTS ARE STREET-WISE VALUES. */
|
||||
inline time_t t (int const year, int const month, int const day)
|
||||
{
|
||||
return t (year, month, day, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Every trade position open and close, and every market-closing price,
|
||||
* is expressed as an \c Event: simply a time/value pair. */
|
||||
struct Event
|
||||
{
|
||||
Time_Point time;
|
||||
Currency_Value price {0.0};
|
||||
|
||||
constexpr Event (const Time_Point& t, const Currency_Value& c)
|
||||
: time {t}, price {c}
|
||||
{}
|
||||
|
||||
Event (const time_t t, const Currency_Value& c)
|
||||
: Event {chrono::system_clock::from_time_t (t), c}
|
||||
{}
|
||||
|
||||
constexpr Event () = default;
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** A vector of (time, price) pairs representing the value history of a
|
||||
* commodity. It is a class invariant that the vector will ALWAYS be
|
||||
* sorted with later dates at the front, earlier ones at the back (the
|
||||
* motivation being that when the history needs to be extended, it will
|
||||
* invariably be extended backwards in time and then the new data will
|
||||
* simply be appended to the existing data vector). */
|
||||
|
||||
struct Time_Series : vector <Event>
|
||||
{
|
||||
/** A general-purpose transparent data object used to demarcate a box
|
||||
* in (time x price) space, usually used to indicate the achieved
|
||||
* bounds of a \c Time_Series. Note that \c start_time is
|
||||
* numerically less than \c end_time. */
|
||||
|
||||
struct Range
|
||||
{
|
||||
Time_Point start_time;
|
||||
Time_Point end_time;
|
||||
Currency_Value min_value {0};
|
||||
Currency_Value max_value {0};
|
||||
|
||||
/** Is the event \a e inside or on the border of this range? */
|
||||
constexpr bool contains (const Event& e) const
|
||||
{ return start_time <= e.time && end_time >= e.time
|
||||
&& min_value <= e.price && max_value >= e.price; }
|
||||
|
||||
constexpr bool operator!= (const Time_Series::Range& a) const
|
||||
{ return a.start_time != start_time || a.end_time != end_time
|
||||
|| a.min_value != min_value || a.max_value != max_value; }
|
||||
};
|
||||
|
||||
|
||||
/** The number of seconds after midnight that the market from which
|
||||
* this time-series derives closes. */
|
||||
Duration market_close_time;
|
||||
|
||||
|
||||
/** Effectively our null constructor, creating an empty time
|
||||
* series. */
|
||||
explicit Time_Series (const Duration& m) : market_close_time {m}
|
||||
{}
|
||||
|
||||
|
||||
/** The one useful (named) class constructor. Manufacture a new
|
||||
* time-series extracted from the database. The time-span will be
|
||||
* from \a latest_time and spanning \a window_size'd time interval to
|
||||
* the past. The \a market_close_time is added to the date of all
|
||||
* data read from the closing prices table. */
|
||||
static Time_Series from_database (DB &db,
|
||||
const int seqid,
|
||||
const Time_Point& latest_time,
|
||||
const Duration& window_size,
|
||||
const Duration& market_close_time);
|
||||
|
||||
|
||||
/** Get more data from the database, extending the length of the time
|
||||
* series we are holding further back in time, from our latest datum
|
||||
* to a distance \a window_size back in time. */
|
||||
void extend_range (DB &db,
|
||||
const int seqid,
|
||||
const Duration& window_size);
|
||||
|
||||
|
||||
/** Get the value of the commodity at a point in time, linearly
|
||||
* interpolated between the two closest spanning points recorded in
|
||||
* the time-series. */
|
||||
Currency_Value interpolated_value (const Time_Point& date) const;
|
||||
|
||||
|
||||
/** Get a \c Range object which boxes the time-series data up to an
|
||||
* interval of \a date_range into the past. */
|
||||
Range get_range (const Duration& date_range) const;
|
||||
|
||||
|
||||
/** Get a \c Range object which boxes the entire time-series data
|
||||
* (this is actually dealt with as a special case inside the above
|
||||
* method). */
|
||||
Range get_range () const
|
||||
{ return get_range (chrono::seconds::max ()); }
|
||||
|
||||
|
||||
/** Insert \a e into its correct place in the current time-series.
|
||||
* This will likely be an expensive operation. */
|
||||
void insert_event (const Event& e);
|
||||
|
||||
|
||||
/** Produce a new time series by applying a moving average of size \a
|
||||
* window to the \a incoming one, which is left unchanged. The
|
||||
* returned series only goes back as far as \a earliest_time. */
|
||||
static Time_Series compute_moving_average (const Time_Series& incoming,
|
||||
const Duration& window,
|
||||
const Time_Point& earliest_time);
|
||||
|
||||
|
||||
}; /* End of class Time_Series. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__TIME_SERIES__H. */
|
||||
80
trader-desk/trade-instruction.cc
Normal file
80
trader-desk/trade-instruction.cc
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include "../trader-desk/trade-instruction.h"
|
||||
#include <sstream>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of \c Trade_Instruction class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Currency_Value Trade_Instruction::value () const
|
||||
{
|
||||
Currency_Value ret;
|
||||
istringstream (entry.get_text ()) >> ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Trade_Instruction::chart_data_changed ()
|
||||
{
|
||||
if (! chart_data.prices.empty ())
|
||||
{
|
||||
ostringstream hold;
|
||||
|
||||
hold << (chart_data.latest_price.price > 0.0
|
||||
? chart_data.latest_price.price
|
||||
: chart_data.prices.front ().price);
|
||||
|
||||
entry.set_text (hold.str ().c_str ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Trade_Instruction::Trade_Instruction (Preferences& P,
|
||||
Chart_Data &cd) : chart_data (cd)
|
||||
{
|
||||
pack_start (main_label, Gtk::PACK_SHRINK, 0);
|
||||
pack_start (entry, Gtk::PACK_SHRINK, 0);
|
||||
pack_start (units_label, Gtk::PACK_SHRINK, 0);
|
||||
|
||||
entry.set_width_chars (4);
|
||||
|
||||
/* Set up the trade_now_button. */
|
||||
chart_data_changed ();
|
||||
|
||||
entry.signal_activate ()
|
||||
.connect ([this, &P]
|
||||
{ DB db {P};
|
||||
chart_data.note_current_price (db, value ()); });
|
||||
|
||||
chart_data.changed_signal
|
||||
.connect ([this] { chart_data_changed (); });
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
96
trader-desk/trade-instruction.h
Normal file
96
trader-desk/trade-instruction.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__TRADE_INSTRUCTION__H
|
||||
#define DMBCS__TRADER_DESK__TRADE_INSTRUCTION__H
|
||||
|
||||
|
||||
#include <trader-desk/chart-data.h>
|
||||
#include <gtkmm.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Trade_Instruction class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** A component of a \c Hand_Analysis_Widget which entirely looks after
|
||||
* itself (interacting with the rest of the program through signals),
|
||||
* and allows the user to set the current price of a commodity, and to
|
||||
* indicate that /buy/ and /sell/ actions have taken place on that
|
||||
* current price.
|
||||
*
|
||||
* It is expected that the owning \c Hand_Analysis_Widget will maintain
|
||||
* the \c Chart_Data object given to the constructor for the lifetime
|
||||
* of one of these objects. */
|
||||
|
||||
class Trade_Instruction : public Gtk::HBox
|
||||
{
|
||||
/** The data we have on the commodity being watched. We react to any
|
||||
* changes in these via the \c chart_data_changed method. */
|
||||
Chart_Data &chart_data;
|
||||
|
||||
/** String printed in front of information box. */
|
||||
Gtk::Label main_label {"Current price "};
|
||||
|
||||
/** String printed after information box. */
|
||||
Gtk::Label units_label {"p "};
|
||||
|
||||
/** The user edit box. Updates from the user are sent directly to \c
|
||||
* chart_data. */
|
||||
Gtk::Entry entry;
|
||||
|
||||
|
||||
/** The user has pressed the buy/sell button. */
|
||||
void trade_now_pressed ();
|
||||
|
||||
/** A change in the chart (price history of commodity of interest) has
|
||||
* taken place. */
|
||||
void chart_data_changed ();
|
||||
|
||||
|
||||
/** Get the contents of the edit box as a currency (pence) value. */
|
||||
Currency_Value value () const;
|
||||
|
||||
|
||||
/* Duplicating one of these objects is both pointless and dangerous. */
|
||||
Trade_Instruction (Trade_Instruction const &) = delete;
|
||||
Trade_Instruction &operator= (Trade_Instruction const &) = delete;
|
||||
Trade_Instruction (Trade_Instruction &&) = delete;
|
||||
Trade_Instruction &operator= (Trade_Instruction &&) = delete;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/** Sole constructor which registers the \a chart_data object we
|
||||
* follow, and sets up the GTK widget ready for use. */
|
||||
Trade_Instruction (Preferences&, Chart_Data &);
|
||||
|
||||
|
||||
}; /* End of class Trade_Instruction. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__TRADE_INSTRUCTION__H. */
|
||||
513
trader-desk/trader-desk.cc
Normal file
513
trader-desk/trader-desk.cc
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include "auto-config.h"
|
||||
#include "alpha-vantage--monitor.h"
|
||||
#include "application.h"
|
||||
#include "markets.h"
|
||||
#include "update-latest-prices.h"
|
||||
#include "wizard.h"
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Window object, and the applicationʼs \c main
|
||||
* entry point. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
static void grid_injector (Chart_Grid &grid,
|
||||
const Update_Latest_Prices::Data& data)
|
||||
{
|
||||
Chart *const c {grid.find_chart (data.company_seqid)};
|
||||
|
||||
if (c) c->data.new_event ({chrono::system_clock::to_time_t (data.time),
|
||||
data.price});
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct Window : Gtk::Window
|
||||
{
|
||||
/** All of the pieces that make up this GTK Window application,
|
||||
* including widgets that go in the window, engine parts, and
|
||||
* glue. */
|
||||
Application app;
|
||||
|
||||
/** Top-level layout: menu stacked on top of a notebook. */
|
||||
Gtk::VBox v_box;
|
||||
Gtk::HBox menu_strip;
|
||||
Gtk::HBox alphavantage_clocks;
|
||||
|
||||
|
||||
explicit Window (Preferences&&);
|
||||
|
||||
void update_latest_data ();
|
||||
|
||||
void change_company_name (Chart_Data& chart_data, const int& seqid);
|
||||
|
||||
void create_application (Preferences&& P);
|
||||
|
||||
void destroy_application ();
|
||||
|
||||
|
||||
}; /* End of Window class. */
|
||||
|
||||
|
||||
static void show_about ()
|
||||
{
|
||||
Gtk::AboutDialog a;
|
||||
a.set_program_name (PACKAGE);
|
||||
a.set_version (VERSION);
|
||||
a.set_copyright
|
||||
(gettext ("Copyright (c) Dale Mellor 2017, 2020\nGPLv3+ applies"));
|
||||
a.set_documenters (vector<Glib::ustring> {"Dale Mellor"});
|
||||
a.set_license_type (Gtk::LICENSE_GPL_3_0);
|
||||
a.set_website ("https://rdmp.org/trader-desk");
|
||||
a.set_authors (vector<Glib::ustring> {"Dale Mellor"});
|
||||
a.set_logo (Gdk::Pixbuf::create_from_file (PKGDATADIR "/trader-desk.png")
|
||||
->scale_simple (100, 100, Gdk::INTERP_NEAREST));
|
||||
a.run ();
|
||||
}
|
||||
|
||||
|
||||
static void run_wizard
|
||||
(Preferences& P,
|
||||
const bool force,
|
||||
std::function<void (Preferences&)> post_process = {})
|
||||
{
|
||||
Wizard *const W {new Wizard {P.file_path, force}};
|
||||
/* W->set_transient_for (*this); */
|
||||
/* W->set_attached_to (*this); */
|
||||
W->set_modal (1);
|
||||
W->show ();
|
||||
W->signal_unmap ().connect ([W, &P, post_process]
|
||||
{ P = W->database_prefs.prefs;
|
||||
dump (P);
|
||||
if (post_process) post_process (P);
|
||||
delete W; });
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void run_preferences_dialog
|
||||
(Window& W, const string& alpha_vantage_message = {})
|
||||
{
|
||||
auto D {alpha_vantage_message.length ()
|
||||
? new Preferences_Dialog {W, W.app.user_prefs,
|
||||
alpha_vantage_message}
|
||||
: new Preferences_Dialog {W, W.app.user_prefs}};
|
||||
D->signal_hide ()
|
||||
.connect ([&W, D, P=W.app.user_prefs]
|
||||
{ delete D;
|
||||
run_wizard (W.app.user_prefs, 1,
|
||||
[&W, P] (Preferences& newP)
|
||||
{
|
||||
if (! database_equal (P, newP))
|
||||
{
|
||||
W.destroy_application ();
|
||||
W.create_application (Preferences {newP});
|
||||
}
|
||||
}); });
|
||||
D->show ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void run_unforced_wizard (Window& W)
|
||||
{
|
||||
const auto P {W.app.user_prefs};
|
||||
|
||||
run_wizard (W.app.user_prefs, 0,
|
||||
[&W, P] (Preferences& newP)
|
||||
{
|
||||
if (! database_equal (P, newP))
|
||||
{
|
||||
W.destroy_application ();
|
||||
W.create_application (Preferences {newP});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Window::change_company_name (Chart_Data& chart_data, const int& seqid)
|
||||
{
|
||||
unique_ptr<DB> db;
|
||||
|
||||
for (auto &a : app.market_grids)
|
||||
{
|
||||
Chart *const c {a->find_chart (seqid)};
|
||||
|
||||
if (c) { chart_data.subsume (&c->data);
|
||||
if (! db) db = make_unique<DB> (app.user_prefs);
|
||||
app . hand_analysis -> company_name
|
||||
. read_names (*db, a->market.seqid, seqid);
|
||||
return; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Window::create_application (Preferences&& P)
|
||||
{
|
||||
app.window = this;
|
||||
app.user_prefs = std::move (P);
|
||||
|
||||
app.preferences_error
|
||||
= [this] (const string& alpha_vantage_message)
|
||||
{ run_preferences_dialog (*this, alpha_vantage_message); };
|
||||
|
||||
app.hand_analysis
|
||||
= make_unique<Hand_Analysis_Widget>
|
||||
([this] (Chart_Data &cd, int const &seqid)
|
||||
{ change_company_name (cd, seqid); },
|
||||
app.user_prefs);
|
||||
|
||||
app.actions = Gtk::ActionGroup::create ();
|
||||
|
||||
app.actions->add (Gtk::Action::create ("quit", Gtk::Stock::QUIT),
|
||||
Gtk::AccelKey {"<control>q"},
|
||||
[this] { close (); });
|
||||
|
||||
app.actions->add (Gtk::Action::create ("file-menu",
|
||||
pgettext ("Menu", "_File")));
|
||||
app.actions->add
|
||||
(Gtk::Action::create ("preferences",
|
||||
pgettext ("Menu", "_Preferences")),
|
||||
Gtk::AccelKey {"<control>p"},
|
||||
[this] { run_preferences_dialog (*this); });
|
||||
app.actions->add
|
||||
(Gtk::Action::create ("wizard",
|
||||
pgettext ("Menu", "_Wizard")),
|
||||
Gtk::AccelKey {"<control>w"},
|
||||
[this] { run_unforced_wizard (*this); });
|
||||
app.actions->add (Gtk::Action::create ("display-menu",
|
||||
pgettext ("Menu", "_Market")));
|
||||
app.actions->add (Gtk::Action::create ("market-menu",
|
||||
pgettext ("Menu", "_Update")));
|
||||
app.actions->add (Gtk::Action::create ("help-menu",
|
||||
pgettext ("Menu", "_Help")));
|
||||
app.actions->add (Gtk::Action::create ("about",
|
||||
pgettext ("Menu", "_About")),
|
||||
&show_about);
|
||||
|
||||
|
||||
DB db {app.user_prefs};
|
||||
for (auto &m : Markets {db})
|
||||
if (m.second.tracked)
|
||||
{
|
||||
const auto i {app.market_grids.size ()};
|
||||
|
||||
if (i == 0) set_title (pgettext ("Label", "Trader Desk")
|
||||
+ string {" : "}
|
||||
+ m.second.world_data.name);
|
||||
|
||||
app . market_grids
|
||||
. emplace_back (make_unique<Chart_Grid>
|
||||
(app.user_prefs, m.second));
|
||||
|
||||
/* This needs to go into a sub-routine as it is required
|
||||
* elsewhere. */
|
||||
|
||||
ostringstream a; a << "display-grid-" << i;
|
||||
|
||||
if (i < 9)
|
||||
{
|
||||
ostringstream b; b << "<control>" << i+1;
|
||||
|
||||
app . actions
|
||||
-> add (Gtk::Action::create (a.str (),
|
||||
m.second.world_data.name),
|
||||
Gtk::AccelKey (b.str ()),
|
||||
[this, i, name = m.second.world_data.name]
|
||||
{ app.display_grid (i);
|
||||
set_title (pgettext ("Label",
|
||||
"Trader Desk")
|
||||
+ string {" : "}
|
||||
+ name); });
|
||||
}
|
||||
|
||||
else
|
||||
app.actions->add (Gtk::Action::create (a.str (),
|
||||
m.second.world_data.name),
|
||||
[this, i, name = m.second.world_data.name]
|
||||
{ app.display_grid (i);
|
||||
set_title (pgettext ("Label",
|
||||
"Trader Desk")
|
||||
+ string {" : "}
|
||||
+ name); });
|
||||
}
|
||||
|
||||
|
||||
app.last_data_menu
|
||||
= Gtk::Action::create ("update-recent",
|
||||
pgettext ("Menu", "Update _latest data"));
|
||||
app.last_data_menu->set_sensitive (0);
|
||||
app.actions->add (app.last_data_menu,
|
||||
[this] { update_latest_data (); });
|
||||
|
||||
app.close_data_menu
|
||||
= Gtk::Action::create ("update-closes",
|
||||
pgettext ("Menu", "Update _close data"));
|
||||
app.actions->add (app.close_data_menu,
|
||||
[this] { app.update_closing_prices (); });
|
||||
|
||||
app.actions->add (Gtk::Action::create ("ingest-new-market",
|
||||
pgettext ("Menu",
|
||||
"_Ingest new market")),
|
||||
[this] { app.ingest_new_market (); });
|
||||
|
||||
app.ui_manager = Gtk::UIManager::create ();
|
||||
|
||||
app.ui_manager->insert_action_group (app.actions);
|
||||
|
||||
add_accel_group (app.ui_manager->get_accel_group ());
|
||||
|
||||
app.ui_manager->add_ui_from_string
|
||||
("<ui>"
|
||||
" <menubar name=\"menu\">"
|
||||
" <menu action=\"file-menu\">"
|
||||
" <menuitem action=\"ingest-new-market\"/>"
|
||||
" <menuitem action=\"preferences\"/>"
|
||||
" <menuitem action=\"wizard\"/>"
|
||||
" <menuitem action=\"quit\"/>"
|
||||
" </menu>"
|
||||
" <menu action=\"display-menu\">"
|
||||
|
||||
+ [this]
|
||||
{ ostringstream a;
|
||||
for (size_t i=0; i < app.market_grids.size (); ++i)
|
||||
a << "<menuitem action=\"display-grid-"
|
||||
<< i << "\"/>\n";
|
||||
return a.str (); } ()
|
||||
+
|
||||
" </menu>"
|
||||
" <menu action=\"market-menu\">"
|
||||
" <menuitem action=\"update-recent\"/>"
|
||||
" <menuitem action=\"update-closes\"/>"
|
||||
" </menu>"
|
||||
" <menu action=\"help-menu\">"
|
||||
" <menuitem action=\"about\"/>"
|
||||
" </menu>"
|
||||
" </menubar>"
|
||||
"</ui>");
|
||||
|
||||
v_box.pack_start (menu_strip, Gtk::PACK_SHRINK);
|
||||
menu_strip.pack_start (*app.ui_manager->get_widget ("/menu"),
|
||||
Gtk::PACK_SHRINK);
|
||||
menu_strip.pack_end (alphavantage_clocks, Gtk::PACK_SHRINK);
|
||||
Alpha_Vantage__Monitor::make_clock_widgets (app.user_prefs,
|
||||
alphavantage_clocks);
|
||||
Glib::signal_timeout()
|
||||
.connect ([] { Alpha_Vantage__Monitor::clocks->update ();
|
||||
return 1; },
|
||||
200);
|
||||
|
||||
|
||||
|
||||
/* v_box.pack_start (*app.ui_manager->get_widget ("/ToolBar"), */
|
||||
/* Gtk::PACK_SHRINK); */
|
||||
|
||||
app.notebook.set_show_tabs (0);
|
||||
app.notebook.set_show_border (0);
|
||||
app.notebook.append_page (*app.hand_analysis);
|
||||
|
||||
if (app.market_grids.empty ())
|
||||
app.notebook.append_page (*Gtk::manage (new Gtk::Label {"No markets"}));
|
||||
else
|
||||
for (auto &g : app.market_grids)
|
||||
app.notebook.append_page (*g);
|
||||
|
||||
v_box.pack_start (app.notebook);
|
||||
add (v_box);
|
||||
|
||||
show_all ();
|
||||
|
||||
app.display_grid (0);
|
||||
|
||||
for (auto &a : app.market_grids)
|
||||
a->selection_signal
|
||||
.connect ([this, &a] { app.hand_analysis->subsume_selected (*a);
|
||||
app.close_data_menu->set_sensitive (0);
|
||||
app.last_data_menu->set_sensitive (1);
|
||||
app.notebook.set_current_page (0); });
|
||||
|
||||
if (app.market_grids.empty ())
|
||||
app.ingest_new_market ();
|
||||
|
||||
set_default_size (650, 420);
|
||||
set_icon_from_file (PKGDATADIR "/trader-desk.png");
|
||||
|
||||
} /* End of create_application. */
|
||||
|
||||
|
||||
|
||||
void Window::destroy_application ()
|
||||
{
|
||||
for (auto *const W : app.notebook.get_children ())
|
||||
app.notebook.remove (*W);
|
||||
app.market_grids.clear ();
|
||||
app.hand_analysis.reset ();
|
||||
for (auto *const W : menu_strip.get_children ())
|
||||
menu_strip.remove (*W);
|
||||
v_box.remove (menu_strip);
|
||||
Alpha_Vantage__Monitor::remove_clock_widgets (alphavantage_clocks);
|
||||
v_box.remove (app.notebook);
|
||||
remove (/* v_box */);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Window::update_latest_data ()
|
||||
try
|
||||
{
|
||||
DB db {app.user_prefs};
|
||||
|
||||
if (app.notebook.get_current_page () == 0)
|
||||
{
|
||||
Update_Latest_Prices::do_update (db,
|
||||
app.hand_analysis->chart.data,
|
||||
app.user_prefs);
|
||||
return;
|
||||
}
|
||||
|
||||
Chart_Grid *const market
|
||||
{app.market_grids [app.notebook.get_current_page ()-1].get ()};
|
||||
|
||||
Update_Latest_Prices::Work update {market->market};
|
||||
|
||||
do_update (&update,
|
||||
db,
|
||||
app.user_prefs,
|
||||
[&db, market]
|
||||
(const Update_Latest_Prices::Data& data) -> void
|
||||
{ sql_injector (db, data);
|
||||
grid_injector (*market, data); });
|
||||
}
|
||||
catch (const Alpha_Vantage::Error& E)
|
||||
{
|
||||
Gtk::MessageDialog
|
||||
{*this,
|
||||
gettext ("There seems to be a problem with the AlphaVantage "
|
||||
"account, please check your settings on the "
|
||||
"preferences panel. The message from the "
|
||||
"server is:") + string {"\n\n‘"} + E.what () + "’",
|
||||
0,
|
||||
Gtk::MESSAGE_ERROR}
|
||||
.run ();
|
||||
run_preferences_dialog (*this);
|
||||
}
|
||||
catch (Update_Closing_Prices::No_Connection const &)
|
||||
{
|
||||
Gtk::MessageDialog {*this,
|
||||
gettext ("No Internet Connection"),
|
||||
0,
|
||||
Gtk::MESSAGE_WARNING}
|
||||
.run ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Window::Window (Preferences&& P) : app {std::move (P)}
|
||||
{
|
||||
signal_map_event ()
|
||||
.connect ([this] (GdkEventAny*) -> bool
|
||||
{ run_wizard (app.user_prefs, 1,
|
||||
[this] (Preferences& P)
|
||||
{ create_application (Preferences {P}); });
|
||||
return 0; } );
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
|
||||
int main (int argc, char **argv)
|
||||
try
|
||||
{
|
||||
setlocale (LC_ALL, "");
|
||||
bindtextdomain (PACKAGE, LOCALEDIR);
|
||||
textdomain (PACKAGE);
|
||||
|
||||
/* Needed for Alpha_Vantage. */
|
||||
curlpp::Cleanup curl_lifetime;
|
||||
|
||||
std::string config_file;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
using std::cout;
|
||||
|
||||
if (argv [1] == std::string {"--config"}
|
||||
|| argv [1] == std::string {"-c"})
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
std::cerr << PACKAGE_STRING
|
||||
<< "Error: -c option requires an argument.\n";
|
||||
exit (1);
|
||||
}
|
||||
config_file = argv [2];
|
||||
}
|
||||
|
||||
else if (argv [1] == std::string ("--version"))
|
||||
{
|
||||
cout << PACKAGE_STRING << '\n';
|
||||
cout << gettext ("Copyright (C) 2017, 2020 Dale Mellor") << "\n\n"
|
||||
<< gettext ("License GPLv3+: GNU GPL version 3 or later "
|
||||
"<http://gnu.org/licenses/gpl.html>\n"
|
||||
"This is free software: you are free to change "
|
||||
"and redistribute it.\n"
|
||||
"There is NO WARRANTY, to the extent permitted "
|
||||
"by law.\n");
|
||||
exit (0);
|
||||
}
|
||||
else if (argv [1] == std::string ("--help"))
|
||||
{
|
||||
cout << gettext ("usage") << ": trader-desk [options]\n";
|
||||
cout << gettext ("To report bugs or contact the authors please "
|
||||
"refer to http://rdmp.org/trader-desk\n");
|
||||
exit (0);
|
||||
}
|
||||
}
|
||||
|
||||
Glib::thread_init ();
|
||||
|
||||
Gtk::Main kit (argc, argv);
|
||||
|
||||
namespace TD = DMBCS::Trader_Desk;
|
||||
TD::Window window {config_file.empty ()
|
||||
? TD::Preferences::from_default_file ()
|
||||
: TD::Preferences::from_file (config_file)};
|
||||
|
||||
Gtk::Main::run (window);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
catch (std::runtime_error const &e)
|
||||
{
|
||||
std::cerr << e.what () << ".\n";
|
||||
std::exit (1);
|
||||
}
|
||||
193
trader-desk/update-closing-prices.cc
Normal file
193
trader-desk/update-closing-prices.cc
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/update-closing-prices.h>
|
||||
#include <trader-desk/alpha-vantage--monitor.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Update_Closing_Prices class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk { namespace Update_Closing_Prices {
|
||||
|
||||
|
||||
void sql_injector (DB& db, const Data& data)
|
||||
{
|
||||
db.quick () << "replace into prices set date=\""
|
||||
<< data.year << "-" << data.month << "-" << data.day
|
||||
<< "\", open=" << data.open
|
||||
<< ", high=" << data.high
|
||||
<< ", low=" << data.low
|
||||
<< ", close=" << data.close
|
||||
<< ", volume=" << data.volume
|
||||
<< ", adjusted_close=" << data.adj_close
|
||||
<< ", company=" << data.company_seqid;
|
||||
|
||||
db.quick () << "update company set last_close_date=greatest(\""
|
||||
<< data.year << "-" << data.month << "-" << data.day
|
||||
<< "\", last_close_date) where seqid=" << data.company_seqid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
vector<Company> entries_from_database (DB& db, const size_t market_seqid)
|
||||
{
|
||||
auto sql {db.row_query ()};
|
||||
|
||||
sql << "select rtrim(name), "
|
||||
<< " symbol, "
|
||||
<< " seqid, "
|
||||
<< " unix_timestamp(greatest(date_add(current_date(),"
|
||||
<< " interval -" << 10/* TIME_HORIZON */ << " year),"
|
||||
<< "last_close_date)) "
|
||||
<< " from company "
|
||||
<< " where market=" << market_seqid;
|
||||
|
||||
sql.execute ();
|
||||
|
||||
vector <Company> entries;
|
||||
entries.reserve (sql.number_rows ());
|
||||
|
||||
for (; sql; ++sql)
|
||||
{
|
||||
Company e;
|
||||
sql >> e.name >> e.symbol >> e.seqid >> e.last_close_date;
|
||||
entries.push_back (move (e));
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static tm current_tm ()
|
||||
{
|
||||
using C = chrono::system_clock;
|
||||
const time_t current_t {C::to_time_t (C::now ())};
|
||||
return *localtime (¤t_t);
|
||||
}
|
||||
|
||||
static tm tm_from (const time_t date)
|
||||
{
|
||||
tm start {*localtime (&date)};
|
||||
/* ++ start.tm_mday; */
|
||||
/* mktime (&start); */
|
||||
return start;
|
||||
}
|
||||
|
||||
static bool same_day (const tm& A, const tm& B)
|
||||
{
|
||||
return A.tm_year == B.tm_year && A.tm_mon == B.tm_mon
|
||||
&& A.tm_mday == B.tm_mday;
|
||||
}
|
||||
|
||||
static tm not_weekend (tm&& T)
|
||||
{
|
||||
mktime (&T);
|
||||
if (T.tm_wday == 0) T.tm_mday -= 2;
|
||||
else if (T.tm_wday == 6) T.tm_mday -= 1;
|
||||
else return T;
|
||||
mktime (&T);
|
||||
return T;
|
||||
}
|
||||
|
||||
static tm day_before (tm& T)
|
||||
{
|
||||
mktime (&T);
|
||||
T.tm_mday -= 1;
|
||||
mktime (&T);
|
||||
return T;
|
||||
}
|
||||
|
||||
|
||||
static void do_update
|
||||
(Work& ucp,
|
||||
Preferences& user_prefs,
|
||||
const function <void (double, const Company&)> progress_callback,
|
||||
const function <void (const Data&)> injector,
|
||||
const function <void (const int& company_seqid)> done_processing,
|
||||
const vector<Company>& entries)
|
||||
{
|
||||
ucp.stop = false;
|
||||
|
||||
for (size_t i {0}; i < entries.size (); ++i)
|
||||
{
|
||||
if (ucp.stop) break;
|
||||
|
||||
const Company& company {entries [i]};
|
||||
|
||||
if (progress_callback)
|
||||
progress_callback (i / double (entries.size ()), company);
|
||||
|
||||
tm now {not_weekend (current_tm ())};
|
||||
const auto close_hour {chrono::duration_cast<chrono::hours>
|
||||
(ucp.market.world_data.close_time).count ()};
|
||||
const auto close_min {chrono::duration_cast<chrono::minutes>
|
||||
(ucp.market.world_data.close_time).count ()
|
||||
% 60};
|
||||
if (now.tm_hour < close_hour
|
||||
|| (now.tm_hour == close_hour && now.tm_min < close_min))
|
||||
now = day_before (now);
|
||||
|
||||
if (!same_day (tm_from (company.last_close_date), now))
|
||||
try { while (Price_Server::get_closing_prices
|
||||
(company,
|
||||
ucp.market.world_data.component_extension,
|
||||
user_prefs,
|
||||
injector)
|
||||
== Price_Server::TO_DO::MORE_WORK)
|
||||
if (ucp.stop) break; }
|
||||
|
||||
catch (const Price_Server::Bad_API_Key&) { throw; }
|
||||
|
||||
catch (const Price_Server::Error&)
|
||||
{ cerr << "Skipping company " << company.name << ".\n"; }
|
||||
|
||||
/* This will be thrown by curlpp if there is any serious problem
|
||||
* with the networking. */
|
||||
catch (const exception& e) { throw No_Connection {e}; }
|
||||
|
||||
if (ucp.stop) break;
|
||||
if (done_processing) done_processing (company.seqid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void do_update
|
||||
(Work& ucp,
|
||||
DB& db,
|
||||
Preferences& user_prefs,
|
||||
const function <void (double, const Company&)> progress_callback,
|
||||
const function <void (const Data&)> injector,
|
||||
const function <void (const int& company_seqid)> company_done)
|
||||
{
|
||||
return do_update (ucp,
|
||||
user_prefs,
|
||||
progress_callback,
|
||||
injector,
|
||||
company_done,
|
||||
entries_from_database (db, ucp.market.seqid));
|
||||
}
|
||||
|
||||
|
||||
} } /* End of namespace DMBCS::Trader_Desk::Update_Closing_Prices. */
|
||||
155
trader-desk/update-closing-prices.h
Normal file
155
trader-desk/update-closing-prices.h
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__UPDATE_CLOSING_PRICES__H
|
||||
#define DMBCS__TRADER_DESK__UPDATE_CLOSING_PRICES__H
|
||||
|
||||
|
||||
#include <trader-desk/markets.h>
|
||||
#include <atomic>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Update_Closing_Prices class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
struct Alpha_Vantage__Monitor;
|
||||
using Price_Server = Alpha_Vantage__Monitor;
|
||||
|
||||
|
||||
/** Really a function object, the implementation being the \c run
|
||||
* method. The purpose of the combined function is to scan the
|
||||
* database for all companies in some market, use the Yahoo! API to get
|
||||
* the latest prices for that company, and then ‘inject’ the results to
|
||||
* some output destination, most likely back to the database itself,
|
||||
* but also maybe to working \c Chart_Data objects. */
|
||||
|
||||
namespace Update_Closing_Prices
|
||||
{
|
||||
/** We will throw this object any time we canʼt get service from
|
||||
* Yahoo!. */
|
||||
struct No_Connection : runtime_error
|
||||
{
|
||||
explicit No_Connection (const exception& E)
|
||||
: runtime_error {pgettext ("Error",
|
||||
"Cannot get data from Internet")
|
||||
+ string {": "} + E.what ()}
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
/** Vessel to hold the data which comes back from the Yahoo!
|
||||
* server. */
|
||||
struct Data
|
||||
{
|
||||
/** Our own database sequence ID. */
|
||||
int company_seqid;
|
||||
|
||||
/** The date of the closing transaction. */
|
||||
int year, month, day;
|
||||
|
||||
/** The values of the daily amounts. We put all of this into
|
||||
* the database, though the application currently only takes
|
||||
* any notice of the \c close prices. */
|
||||
double open, high, low, close;
|
||||
|
||||
/** The volume of trade on this day--put into the database,
|
||||
* but not currently used by the application. */
|
||||
int volume;
|
||||
|
||||
/** The ‘adjusted’ closing price--put into the database, but
|
||||
* not currently used by the application. */
|
||||
double adj_close;
|
||||
};
|
||||
|
||||
|
||||
/** Vessel to hold information about companies for which we need to
|
||||
* get updated information. */
|
||||
struct Company
|
||||
{
|
||||
/** Human-readable name of the company. */
|
||||
string name;
|
||||
|
||||
/** The companyʼs ticker symbol. */
|
||||
string symbol;
|
||||
|
||||
/** Our database sequence ID for the company. */
|
||||
int seqid;
|
||||
|
||||
/** The time of our most recent close price for this company. */
|
||||
time_t last_close_date;
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct Work
|
||||
{
|
||||
/** Our database sequence ID for the market in question. */
|
||||
const Market_Meta_Data& market;
|
||||
|
||||
/** A flag to tell us to stop processing (may be set from
|
||||
* outside the class). */
|
||||
atomic<bool> stop {false};
|
||||
};
|
||||
|
||||
|
||||
/** Write the \a data to the database. */
|
||||
void sql_injector (DB&, const Data&);
|
||||
|
||||
|
||||
/** Get the list of companies on which to get new data, from the
|
||||
* database based on the market. */
|
||||
vector<Company> entries_from_database (DB&, const size_t market_seqid);
|
||||
|
||||
|
||||
/** Do the work: scan the \a db database for the appropriate \a
|
||||
* companies and currently known closing prices, obtain new price
|
||||
* information from data service and store these back to the database. The
|
||||
* return will list the symbols of companies of which we were unable to
|
||||
* obtain the latest information.
|
||||
*
|
||||
* If given (not \c nullptr), the \a progress_callback will be called
|
||||
* at regular intervals with the percentage of work completed.
|
||||
*
|
||||
* The \a injector will be called for every new datum that needs adding
|
||||
* to a companyʼs price records.
|
||||
*
|
||||
* If not \c nullptr, the \a company_done callback will be called after
|
||||
* all data for a particular company have been processed as above. */
|
||||
void do_update
|
||||
(Work&,
|
||||
DB&,
|
||||
Preferences&,
|
||||
const function <void (double, const Company&)> progress_callback,
|
||||
const function <void (const Data&)> injector,
|
||||
const function <void (const int& company_seqid)> company_done);
|
||||
|
||||
|
||||
}; /* End of namespace Update_Closing_Prices. */
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__UPDATE_CLOSING_PRICES__H. */
|
||||
118
trader-desk/update-latest-prices.cc
Normal file
118
trader-desk/update-latest-prices.cc
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/update-latest-prices.h>
|
||||
#include <trader-desk/alpha-vantage--monitor.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Implementation of the \c Update_Latest_Prices class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk::Update_Latest_Prices {
|
||||
|
||||
|
||||
using Data_Server = Alpha_Vantage__Monitor;
|
||||
|
||||
|
||||
void sql_injector (DB& db, const Data& data)
|
||||
{
|
||||
time_t t = chrono::system_clock::to_time_t (data.time);
|
||||
tm tm;
|
||||
localtime_r (&t, &tm);
|
||||
|
||||
db.quick () << "update company "
|
||||
<< "set last_price=" << data.price << ','
|
||||
<< "last_price_date=\"" << (tm.tm_year+1900) << '-'
|
||||
<< (tm.tm_mon+1)
|
||||
<< '-' << tm.tm_mday << ' '
|
||||
<< tm.tm_hour << ':' << tm.tm_min
|
||||
<< ':' << tm.tm_sec << "\" "
|
||||
<< " where seqid=" << data.company_seqid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void do_update (Work *const work,
|
||||
Preferences& P,
|
||||
function <void (Data const &)> injector,
|
||||
vector<Company> const &entries)
|
||||
try
|
||||
{
|
||||
for (const Company& C : entries)
|
||||
injector (Data_Server::get_latest_data
|
||||
(C, work->market.world_data.component_extension, P));
|
||||
}
|
||||
catch (exception const &e)
|
||||
{
|
||||
throw Update_Closing_Prices::No_Connection {e};
|
||||
}
|
||||
|
||||
|
||||
|
||||
void do_update (Work *const work,
|
||||
DB& db,
|
||||
Preferences& P,
|
||||
function <void (Data const &)> injector)
|
||||
{
|
||||
do_update (work,
|
||||
P,
|
||||
injector,
|
||||
Update_Closing_Prices::entries_from_database
|
||||
(db, work->market.seqid));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void do_update (DB& db, Chart_Data& data, Preferences& P)
|
||||
{
|
||||
auto row {db.row_query ()};
|
||||
row << "select company.symbol, "
|
||||
<< "unix_timestamp(company.last_close_date), "
|
||||
<< "market.component_extension "
|
||||
<< "from company, market "
|
||||
<< "where company.seqid=" << data.company_seqid
|
||||
<< " and market.seqid=company.market";
|
||||
row.execute ();
|
||||
|
||||
Company C;
|
||||
row >> C.symbol >> C.last_close_date;
|
||||
C.seqid = data.company_seqid;
|
||||
|
||||
const string market_symbol {row.next_entry<string> ()};
|
||||
|
||||
const Data D {Data_Server::get_latest_data (C, market_symbol, P)};
|
||||
|
||||
++row;
|
||||
|
||||
{unique_lock L {data.prices_mutex};
|
||||
data.last_fetch_time = D.time;
|
||||
data.prices.insert_event ( {D.time, D.price} );
|
||||
data.extremes.end_time = D.time; }
|
||||
|
||||
data.changed_signal.emit ();
|
||||
}
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk::Update_Latest_Prices. */
|
||||
86
trader-desk/update-latest-prices.h
Normal file
86
trader-desk/update-latest-prices.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS_TRADER_DESK__UPDATE_LATEST_PRICES__H
|
||||
#define DMBCS_TRADER_DESK__UPDATE_LATEST_PRICES__H
|
||||
|
||||
|
||||
#include <trader-desk/chart-data.h>
|
||||
#include <trader-desk/update-closing-prices.h>
|
||||
|
||||
|
||||
/** \file
|
||||
*
|
||||
* Declaration of the \c Update_Latest_Prices class. */
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
/** Function object which reads a list of companies in a market from the
|
||||
* database, fetches the latest prices for those companies from the
|
||||
* Yahoo! service, and then writes the new information back to the
|
||||
* database and gives them back to the calling application.
|
||||
*
|
||||
* Similarities with the \c Update_Closing_Prices class are actually
|
||||
* quite superficial, but we do inherit that class mainly to take
|
||||
* advantage of the functionality for the initial fetch of the company
|
||||
* list from the database. */
|
||||
|
||||
namespace Update_Latest_Prices
|
||||
{
|
||||
/** Vessel to carry the information that comes back from the data
|
||||
* service. */
|
||||
struct Data {
|
||||
/** Our database sequence ID. */
|
||||
int company_seqid;
|
||||
|
||||
/** The time at which the price holds. */
|
||||
Time_Point time;
|
||||
|
||||
/** The current price of this commodity. */
|
||||
double price;
|
||||
};
|
||||
|
||||
|
||||
using Company = Update_Closing_Prices::Company;
|
||||
|
||||
struct Work : Update_Closing_Prices::Work {};
|
||||
|
||||
/** Update the database with the new \a data. */
|
||||
void sql_injector (DB&, const Data&);
|
||||
|
||||
|
||||
/** Get the companies for the registered market, fetch their latest
|
||||
* data from the Yahoo! server, and pass the results, one at a time,
|
||||
* to \a injector. */
|
||||
void do_update (Work *const work,
|
||||
DB&,
|
||||
Preferences&,
|
||||
function <void (const Data&)> injector);
|
||||
|
||||
|
||||
void do_update (DB&, Chart_Data&, Preferences&);
|
||||
|
||||
|
||||
} } /* End of namespace DMBCS::Trader_Desk::Update_Latest_Prices. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS_TRADER_DESK__UPDATE_LATEST_PRICES__H. */
|
||||
989
trader-desk/wizard.cc
Normal file
989
trader-desk/wizard.cc
Normal file
|
|
@ -0,0 +1,989 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#include <trader-desk/wizard.h>
|
||||
#include <trader-desk/db.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
Database_Prefs& Database_Prefs::operator= (const Prefs& P)
|
||||
{
|
||||
prefs = P;
|
||||
host .set_text (P.database_host);
|
||||
socket .set_text (P.database_socket);
|
||||
port .set_text (to_string (P.database_port));
|
||||
database .set_text (P.database_instance);
|
||||
user .set_text (P.database_user);
|
||||
password .set_text (P.database_password);
|
||||
submit.set_sensitive (0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Prefs Database_Prefs::update_prefs (Prefs P)
|
||||
{
|
||||
P.database_host = host.get_text ();
|
||||
P.database_socket = socket.get_text ();
|
||||
P.database_port = atoi (port.get_text ().c_str ());
|
||||
P.database_instance = database.get_text ();
|
||||
P.database_user = user.get_text ();
|
||||
P.database_password = password.get_text ();
|
||||
return P;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void entry_ (Database_Prefs& P, Gtk::Entry& E,
|
||||
const string& label, const int row)
|
||||
{
|
||||
P.attach (*Gtk::manage (new Gtk::Label {label + ": "}), 0, row);
|
||||
E.set_width_chars (30);
|
||||
E.signal_changed ().connect ([&P] {P.submit.set_sensitive ();});
|
||||
E.signal_activate ().connect ([&P] {P.submit.clicked ();});
|
||||
P.attach (E, 1, row);
|
||||
}
|
||||
|
||||
Database_Prefs::Database_Prefs ()
|
||||
{
|
||||
const auto entry
|
||||
{ [this] (Gtk::Entry& E, const string& L, const int R)
|
||||
{ return entry_ (*this, E, L, R); } };
|
||||
|
||||
entry (host, t_("Label", "Host"), 0);
|
||||
entry (socket, t_("Label", "Socket"), 1);
|
||||
|
||||
attach (*Gtk::make_managed<Gtk::Label>
|
||||
(t_("Label", "Port") + string {": "}),
|
||||
0, 2);
|
||||
port.signal_changed ().connect ([this] {submit.set_sensitive ();});
|
||||
port.signal_activate ().connect ([this] {submit.clicked ();});
|
||||
attach (port, 1, 2);
|
||||
|
||||
entry (database, t_("Label", "Database"), 3);
|
||||
|
||||
entry (user, t_("Label", "User"), 4);
|
||||
user.signal_changed ().connect ([this] { prefs.blank_password_forced = 0;
|
||||
password.set_sensitive (); });
|
||||
|
||||
entry (password, t_("Label", "Password"), 5);
|
||||
|
||||
auto *const B {Gtk::make_managed<Gtk::HBox> ()};
|
||||
attach (*B, 0, 6, 2, 1);
|
||||
B->pack_start (submit, Gtk::PACK_EXPAND_PADDING);
|
||||
submit.set_sensitive (0);
|
||||
show_all ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static tuple<unique_ptr<Gtk::Widget>, Gtk::TextBuffer*, Gtk::HBox*>
|
||||
make_panel (Gtk::VBox& V, const string& nature)
|
||||
{
|
||||
auto H {make_unique<Gtk::HBox> (0, 15)};
|
||||
V.pack_start (*H, Gtk::PACK_SHRINK);
|
||||
H->pack_start (*Gtk::make_managed<Gtk::Image> ("dialog-" + nature,
|
||||
Gtk::ICON_SIZE_DIALOG),
|
||||
Gtk::PACK_SHRINK);
|
||||
auto *const V2 {Gtk::make_managed<Gtk::VBox> (0, 15)};
|
||||
H->pack_start (*V2);
|
||||
auto *const T {Gtk::make_managed<Gtk::TextView> ()};
|
||||
V2->pack_start (*T, Gtk::PACK_SHRINK);
|
||||
T->set_wrap_mode (Gtk::WRAP_WORD);
|
||||
T->override_background_color (Gdk::RGBA {"rgba(0,0,0,0)"});
|
||||
auto *const H2 {Gtk::make_managed<Gtk::HBox> ()};
|
||||
V2->pack_start (*H2, Gtk::PACK_SHRINK);
|
||||
return {move (H), T->get_buffer ().get (), H2};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void make_configuration_page (Wizard& W)
|
||||
{
|
||||
W.configuration_page.set_spacing (65);
|
||||
auto V {Gtk::make_managed<Gtk::VBox> (0, 15)};
|
||||
W.configuration_page.pack_start (*V, Gtk::PACK_SHRINK);
|
||||
V->pack_start (*Gtk::make_managed<Gtk::Label>
|
||||
(t_("Label", "Using config file") + string {":"}),
|
||||
Gtk::PACK_SHRINK);
|
||||
V->pack_start (W.config_file_entry, Gtk::PACK_SHRINK);
|
||||
W.config_file_entry.set_width_chars (30);
|
||||
auto *const H {Gtk::make_managed<Gtk::HBox> (0, 15)};
|
||||
V->pack_start (*H, Gtk::PACK_SHRINK);
|
||||
H->pack_start (W.use_default_config_button, Gtk::PACK_EXPAND_PADDING);
|
||||
H->pack_start (W.find_file_button, Gtk::PACK_EXPAND_PADDING);
|
||||
H->pack_start (W.change_button, Gtk::PACK_EXPAND_PADDING);
|
||||
|
||||
W.configuration_page.show_all ();
|
||||
|
||||
{auto [P, T, H] {make_panel (W.configuration_page, "warning")};
|
||||
W.create_file_panel = move (P);
|
||||
T->set_text (gettext ("This file does not exist, shall I create it?"));
|
||||
H->pack_start (W.create_file_button, Gtk::PACK_EXPAND_PADDING);
|
||||
}
|
||||
|
||||
{auto [P, T, H] {make_panel (W.configuration_page, "warning")};
|
||||
W.non_default_panel = move (P);
|
||||
T->set_text (gettext ("This is not the default config file path. "
|
||||
"This means that you will either have to copy "
|
||||
"the file to the default location, or use the "
|
||||
"-c option to specify the file whenever you "
|
||||
"run the trader-desk application."));
|
||||
H->pack_start (W.acknowledge_check, Gtk::PACK_SHRINK);
|
||||
}
|
||||
|
||||
W.config_file_entry.signal_changed ()
|
||||
.connect ([&W] { W.change_button.set_sensitive (); });
|
||||
W.config_file_entry.signal_activate ()
|
||||
.connect ([&W] { W.change_button.clicked (); });
|
||||
}
|
||||
|
||||
|
||||
|
||||
template<typename Callback>
|
||||
static void connect_clicked
|
||||
(sigc::connection& C, Gtk::Button& B, Callback F)
|
||||
{
|
||||
C.disconnect ();
|
||||
C = sigc::connection {B.signal_clicked ().connect (F)};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void check_config_file
|
||||
(Wizard& W, const string& file_name, bool not_default);
|
||||
|
||||
static void config_file_chooser (Wizard& W)
|
||||
{
|
||||
Gtk::FileChooserDialog D {W,
|
||||
t_("Label", "trader-desk config file"),
|
||||
Gtk::FILE_CHOOSER_ACTION_SAVE};
|
||||
D.add_button (t_("Label", "Open"), Gtk::RESPONSE_ACCEPT);
|
||||
D.set_default_response (Gtk::RESPONSE_ACCEPT);
|
||||
D.signal_response ()
|
||||
.connect ([&W, &D] (const int response)
|
||||
{ D.hide ();
|
||||
if (response == Gtk::RESPONSE_ACCEPT)
|
||||
check_config_file (W, D.get_filename (), 0); });
|
||||
D.run ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void check_config_file
|
||||
(Wizard& W, const string& file_name, bool not_default = 0)
|
||||
{
|
||||
W.config_file_entry.set_text (file_name);
|
||||
W.config_file_entry
|
||||
.set_width_chars (max<int> (W.config_file_entry.get_width_chars (),
|
||||
file_name.length ()));
|
||||
W.use_default_config_button
|
||||
.set_sensitive (file_name != Preferences::default_file_name ());
|
||||
connect_clicked
|
||||
(W.udcb_i,
|
||||
W.use_default_config_button,
|
||||
[&W, not_default]
|
||||
{ check_config_file
|
||||
(W, Preferences::default_file_name (), not_default); });
|
||||
connect_clicked (W.ffb_i,
|
||||
W.find_file_button,
|
||||
[&W] { config_file_chooser (W); });
|
||||
W.change_button.set_sensitive (0);
|
||||
connect_clicked
|
||||
(W.cb_i,
|
||||
W.change_button,
|
||||
[&W, not_default]
|
||||
{ check_config_file
|
||||
(W, W.config_file_entry.get_text (), not_default); });
|
||||
ifstream i {file_name};
|
||||
const bool file_exists {i.good ()};
|
||||
if (file_exists)
|
||||
{
|
||||
i.close ();
|
||||
W.database_prefs.prefs = Prefs {Preferences::from_file (file_name)};
|
||||
W.create_file_panel->hide ();
|
||||
}
|
||||
else
|
||||
{
|
||||
W.create_file_panel->show_all ();
|
||||
connect_clicked
|
||||
(W.cfb_i,
|
||||
W.create_file_button,
|
||||
[&W, &E = W.config_file_entry, not_default]
|
||||
{ Preferences::from_file (E.get_text ());
|
||||
check_config_file (W, E.get_text (), not_default);});
|
||||
}
|
||||
|
||||
if (file_name != Preferences::default_file_name ())
|
||||
{
|
||||
((Gtk::Image*) ((Gtk::Container*) W.non_default_panel.get ())
|
||||
->get_children ().front ())
|
||||
->set_from_icon_name (string {"dialog-"}
|
||||
+ (not_default ? "information" : "warning"),
|
||||
Gtk::ICON_SIZE_DIALOG);
|
||||
W.non_default_panel->show_all ();
|
||||
W.acknowledge_check.set_active (not_default);
|
||||
connect_clicked
|
||||
(W.ac_i, W.acknowledge_check,
|
||||
[&W] { check_config_file (W,
|
||||
W.config_file_entry.get_text (),
|
||||
W.acknowledge_check.get_active ()); });
|
||||
}
|
||||
else
|
||||
W.non_default_panel->hide ();
|
||||
|
||||
const bool done
|
||||
{file_exists && (not_default
|
||||
|| file_name == Preferences::default_file_name ())};
|
||||
W.set_page_complete (W.configuration_page, done);
|
||||
if (W.force && done) W.next_page ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static unique_ptr<Gtk::Widget> absolute_message (Gtk::VBox& page,
|
||||
const string& message)
|
||||
{
|
||||
auto [P, T, H] {make_panel (page, "warning")};
|
||||
T->set_text (message);
|
||||
return move (P);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void make_database_page (Wizard& W, Gtk::VBox& page)
|
||||
{
|
||||
page.pack_start (W.database_prefs, Gtk::PACK_SHRINK);
|
||||
page.show_all ();
|
||||
|
||||
{auto [P, T, H] { make_panel (page, "warning") };
|
||||
W.no_password_panel = move (P);
|
||||
T->set_text (gettext ("No password has been set for this user. You are "
|
||||
"strongly recommended to set one. Enter and commit "
|
||||
"a new password above, let me generate a random "
|
||||
"password for you, or press skip if you are sure "
|
||||
"you donʼt need one."));
|
||||
H->pack_start (W.generate_password_button, Gtk::PACK_EXPAND_PADDING);
|
||||
H->pack_start (W.use_no_password_button, Gtk::PACK_EXPAND_PADDING);
|
||||
}
|
||||
|
||||
{auto [P, T, H] { make_panel (page, "information") };
|
||||
W.blank_password_panel = move (P);
|
||||
T->set_text (gettext ("No database password is being used for this "
|
||||
"user."));
|
||||
H->pack_start (W.blank_password_ack_button, Gtk::PACK_SHRINK);
|
||||
}
|
||||
|
||||
W.cannot_access_panel_1
|
||||
= absolute_message
|
||||
(page,
|
||||
gettext ("Cannot access the database with this account. Please "
|
||||
"correct the user and password fields above. You may also "
|
||||
"insert the root password, if you know it, and I will try "
|
||||
"to help sort the problem out; you will need to do this if "
|
||||
"you are trying to create a new user."));
|
||||
|
||||
W.cannot_access_panel_2
|
||||
= absolute_message
|
||||
(page,
|
||||
gettext ("Cannot access the database with this account. Please "
|
||||
"correct the user and password fields above."));
|
||||
|
||||
|
||||
{auto [P, text, buttons] {make_panel (W.database_access_page, "warning")};
|
||||
W.privilege_panel = move (P);
|
||||
text->set_text (gettext ("This user does not have access to this database "
|
||||
"instance. Either modify the settings above or "
|
||||
"provide the root password in the box below"));
|
||||
buttons->pack_start (*Gtk::make_managed<Gtk::Label>
|
||||
(t_("Label", "Database root password: ")));
|
||||
W.database_root_entry.set_width_chars (30);
|
||||
buttons->pack_start (W.database_root_entry, Gtk::PACK_SHRINK);
|
||||
}
|
||||
|
||||
W.database_root_entry
|
||||
.signal_activate ()
|
||||
.connect ([&W]
|
||||
{ W.database_prefs.prefs.root_password
|
||||
= W.database_root_entry.get_text ();
|
||||
W.get_database_connection (W.database_prefs.prefs);});
|
||||
|
||||
|
||||
{auto [P, T, H] {make_panel (W.database_access_page, "warning")};
|
||||
W.create_database_panel = move (P);
|
||||
T->set_text (gettext ("This database instance does not exist. Either "
|
||||
"edit the entry above or select the create "
|
||||
"button."));
|
||||
H->pack_start (W.create_database_button, Gtk::PACK_EXPAND_PADDING);
|
||||
}
|
||||
|
||||
|
||||
{auto [P, T, H] { make_panel (W.database_access_page, "warning") };
|
||||
W.create_password_panel = move (P);
|
||||
T->set_text (gettext ("The user password is not set in the database. "
|
||||
"Do you want me to do that for you now?"));
|
||||
H->pack_start (W.blank_password_button, Gtk::PACK_EXPAND_PADDING);
|
||||
H->pack_start (W.create_password_button, Gtk::PACK_EXPAND_PADDING);
|
||||
}
|
||||
|
||||
|
||||
W.port_problem_panel
|
||||
= absolute_message (page,
|
||||
gettext ("Cannot contact a database on that PORT."));
|
||||
|
||||
W.socket_problem_panel
|
||||
= absolute_message
|
||||
(page, gettext ("Cannot contact a local database on that SOCKET."));
|
||||
|
||||
W.host_problem_panel
|
||||
= absolute_message
|
||||
(page, gettext ("The database host you specified cannot be reached."));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static string slurp_file (ifstream&& input)
|
||||
{
|
||||
input.seekg (0, ios::end);
|
||||
string line (input.tellg (), char {});
|
||||
input.seekg (0, ios::beg);
|
||||
input.read (line.data (), line.length ());
|
||||
return line;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void prepare_data_page (Wizard&);
|
||||
|
||||
static gboolean progress_ticker (gpointer W_)
|
||||
{
|
||||
Wizard& W {*(Wizard*) W_};
|
||||
/* !! Thread local. */
|
||||
static int old_progress = 0;
|
||||
if (W.progress_total < 0)
|
||||
{
|
||||
prepare_data_page (W);
|
||||
return 0;
|
||||
}
|
||||
if (W.progress_count != old_progress)
|
||||
{
|
||||
old_progress = W.progress_count;
|
||||
if (W.progress_total == 0)
|
||||
W.progress_bar.pulse ();
|
||||
else
|
||||
W.progress_bar.set_fraction
|
||||
(W.progress_count / (double) W.progress_total);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void prepare_data_page (Wizard& W)
|
||||
{
|
||||
for (auto *const i : W.data_page.get_children ())
|
||||
W.data_page.remove (*i);
|
||||
|
||||
DB db {W.database_prefs.prefs};
|
||||
if (db.scalar_result<int> (0, "select count(*) from company") != 0)
|
||||
{
|
||||
if (W.force) { W.close (); /* next_page (); */ return; }
|
||||
W.set_page_complete (W.data_page);
|
||||
W.data_page.pack_start (*W.database_ready_panel, Gtk::PACK_SHRINK);
|
||||
W.data_page.show_all ();
|
||||
return;
|
||||
}
|
||||
|
||||
ifstream I {PKGDATADIR "/data.sql.xz"};
|
||||
if (! I.good ())
|
||||
{
|
||||
if (W.force) { W.close (); /* next_page (); */ return; }
|
||||
W.set_page_complete (W.data_page);
|
||||
W.data_page.pack_start (*W.no_data_panel, Gtk::PACK_SHRINK);
|
||||
W.data_page.show_all ();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
W.data_page.pack_start
|
||||
(*Gtk::make_managed<Gtk::Label>
|
||||
(pgettext ("Label", "Pre-populating database")),
|
||||
Gtk::PACK_SHRINK);
|
||||
W.data_page.pack_start (W.progress_bar, Gtk::PACK_SHRINK);
|
||||
W.show_all ();
|
||||
|
||||
gdk_threads_add_timeout (250, progress_ticker, &W);
|
||||
|
||||
std::thread
|
||||
{
|
||||
[&W]
|
||||
{
|
||||
DB db {W.database_prefs.prefs};
|
||||
/* !! Would prefer to call out to xz library. */
|
||||
system ("cat " PKGDATADIR "/data.sql.xz "
|
||||
"| xz -d "
|
||||
"> /tmp/trader-desk--data.sql");
|
||||
const string sql
|
||||
{slurp_file (ifstream {"/tmp/trader-desk--data.sql"})};
|
||||
unlink ("/tmp/trader-desk--data.sql");
|
||||
W.progress_total = 0;
|
||||
W.progress_count = 0;
|
||||
for (size_t a {0}; ; )
|
||||
{
|
||||
const size_t b {sql.find ("INSERT", a)};
|
||||
if (b == sql.npos) break;
|
||||
++W.progress_count;
|
||||
a = b + 1;
|
||||
}
|
||||
W.progress_total = W.progress_count.load ();
|
||||
W.progress_count = 0;
|
||||
for (size_t a {0}; ; )
|
||||
{
|
||||
const size_t b {sql.find ("INSERT", a)};
|
||||
if (b == sql.npos) break;
|
||||
const size_t c {sql.find (";", b)};
|
||||
db.instruction (sql.substr (b, c-b));
|
||||
++W.progress_count;
|
||||
a = b + 1;
|
||||
}
|
||||
W.progress_total = -1;
|
||||
}
|
||||
} . detach ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool do_tables (Wizard& W, const bool build)
|
||||
try
|
||||
{
|
||||
Prefs& P {W.database_prefs.prefs};
|
||||
DB db {P};
|
||||
|
||||
if (1 == db.scalar_result (0, "select version from global"))
|
||||
return 1;
|
||||
|
||||
if (! build) return 0;
|
||||
|
||||
db.instruction ("create table global "
|
||||
"(version int, "
|
||||
"last_markets_update datetime default 0)");
|
||||
|
||||
db.instruction ("insert into global "
|
||||
"set version=1");
|
||||
|
||||
db.instruction ("create table company "
|
||||
"(seqid int(6) primary key auto_increment, "
|
||||
"name varchar(50) not null, "
|
||||
"symbol varchar(6) not null, "
|
||||
"market int(6) not null default 0, "
|
||||
"last_price float, "
|
||||
"last_price_date datetime, "
|
||||
"last_close_date date not null "
|
||||
"default '0000-00-00')");
|
||||
|
||||
db.instruction ("create table prices "
|
||||
"(date date, "
|
||||
"company int(6), "
|
||||
"open float, high float, low float, "
|
||||
"close float, "
|
||||
"volume int(11), adjusted_close float)");
|
||||
|
||||
db.instruction ("alter table prices "
|
||||
"add primary key (date, company)");
|
||||
|
||||
db.instruction ("create table market "
|
||||
"(seqid int(6) primary key auto_increment, "
|
||||
"symbol varchar(6), "
|
||||
"name varchar(36), "
|
||||
"component_extension varchar(6), "
|
||||
"last_update datetime default 0, "
|
||||
"tracked bool default 0, "
|
||||
"close_time time)");
|
||||
|
||||
db.instruction ("create table alphavantage_ticks "
|
||||
"(time int(11) primary key)");
|
||||
|
||||
return 1;
|
||||
}
|
||||
catch (exception&) { return 0; }
|
||||
|
||||
|
||||
|
||||
static void prepare_table_page (Wizard& W)
|
||||
{
|
||||
do_tables (W, 1);
|
||||
W.set_page_complete (W.tables_page);
|
||||
if (W.force) { W.next_page (); return; }
|
||||
Gtk::TextView *const T {Gtk::make_managed<Gtk::TextView> ()};
|
||||
W.tables_page.add (*T);
|
||||
T->get_buffer ()->set_text ("The database tables are in place.");
|
||||
W.tables_page.show_all ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
int Wizard::page_order (Gtk::Widget *const a, Gtk::Widget *const b)
|
||||
{
|
||||
if (a == 0 || b == 0 || a == b) return 0;
|
||||
if (a == (Gtk::Widget*)&configuration_page)
|
||||
return -1;
|
||||
if (a == (Gtk::Widget*)&database_access_page)
|
||||
return b == (Gtk::Widget*)&configuration_page ? 1 : -1;
|
||||
if (a == (Gtk::Widget*)&tables_page)
|
||||
return b == (Gtk::Widget*)&data_page ? -1 : 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void make_data_page (Wizard& W, Gtk::VBox& page)
|
||||
{
|
||||
{auto [P, T, H] { make_panel (page, "information") };
|
||||
W.no_data_panel = move (P);
|
||||
T->set_text (gettext ("There are no data available to pre-populate "
|
||||
"the database. You will be invited to import "
|
||||
"data from the Internet."));
|
||||
}
|
||||
|
||||
{auto [P, T, H] { make_panel (page, "information") };
|
||||
W.database_ready_panel = move (P);
|
||||
T->set_text (gettext ("The database is populated with some data."));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Wizard::Wizard (const string& config_file_name, const bool f) : force {f}
|
||||
{
|
||||
const auto add_page
|
||||
{ [&W=*this] (Gtk::VBox& V, const string& title)
|
||||
{ W.append_page (V);
|
||||
W.set_page_type (V, Gtk::ASSISTANT_PAGE_CONTENT);
|
||||
W.set_page_title (V, title);
|
||||
V.set_spacing (40); } };
|
||||
|
||||
add_page (configuration_page, t_("Label", "Config file"));
|
||||
make_configuration_page (*this);
|
||||
|
||||
add_page (database_access_page, t_("Label", "Database access"));
|
||||
make_database_page (*this, database_access_page);
|
||||
|
||||
add_page (tables_page, t_("Label", "Tables"));
|
||||
|
||||
add_page (data_page, t_("Label", "Historical data"));
|
||||
make_data_page (*this, data_page);
|
||||
set_page_type (data_page, Gtk::ASSISTANT_PAGE_CONFIRM);
|
||||
|
||||
database_prefs.submit
|
||||
.signal_clicked ()
|
||||
.connect ([this]
|
||||
{ database_prefs.update_prefs ();
|
||||
get_database_connection
|
||||
(database_prefs.prefs); });
|
||||
|
||||
signal_close ()
|
||||
.connect ([this] { signal_wizard_complete.emit (database_prefs.prefs);
|
||||
hide (); });
|
||||
|
||||
signal_cancel ().connect ([this] {hide ();});
|
||||
show_all ();
|
||||
set_icon_from_file (PKGDATADIR "/trader-desk.png");
|
||||
signal_prepare ()
|
||||
.connect ([this] (Gtk::Widget *const page)
|
||||
{ if (page_order (last_page, page) > 0) force = 0;
|
||||
last_page = page;
|
||||
if (page == (Gtk::Widget*) &configuration_page)
|
||||
check_config_file (*this,
|
||||
database_prefs.prefs.file_path);
|
||||
else if (page == (Gtk::Widget*) &database_access_page)
|
||||
get_database_connection (database_prefs.prefs);
|
||||
else if (page == (Gtk::Widget*) &tables_page)
|
||||
prepare_table_page (*this);
|
||||
else if (page == (Gtk::Widget*) &data_page)
|
||||
prepare_data_page (*this);});
|
||||
check_config_file (*this, config_file_name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool host_problem (Wizard& W, const string& error)
|
||||
{
|
||||
/* !!!! This probably wouldn't work if the machine is using a
|
||||
* language other than English? */
|
||||
static const regex unknown_host_RE {".*unknown.*server host.*",
|
||||
regex::icase};
|
||||
if (! regex_match (error, unknown_host_RE)) return 0;
|
||||
W.host_problem_panel->show_all ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool socket_problem (Wizard& W, const string& error)
|
||||
{
|
||||
static const regex bad_socket_RE
|
||||
{".*can.t.*connect.*local.*server.*socket.*",
|
||||
regex::icase};
|
||||
if (! regex_match (error, bad_socket_RE)) return 0;
|
||||
W.socket_problem_panel->show_all ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static bool port_problem (Wizard& W, const string& error)
|
||||
{
|
||||
static const regex bad_port_RE {".*can.t.*connect.*server.*",
|
||||
regex::icase};
|
||||
if (! regex_match (error, bad_port_RE)) return 0;
|
||||
W.port_problem_panel->show_all ();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void do_set_password_on_database (Database_Prefs& P)
|
||||
{
|
||||
string old_password {};
|
||||
swap (old_password, P.prefs.database_password);
|
||||
DB {P.prefs}
|
||||
.instruction ("set password=password('" + old_password + "')");
|
||||
swap (old_password, P.prefs.database_password);
|
||||
}
|
||||
|
||||
static bool set_password_on_database (Wizard& W, Database_Prefs& P)
|
||||
{
|
||||
if (W.force) { do_set_password_on_database (P);
|
||||
return 1; }
|
||||
|
||||
W.create_password_panel->show_all ();
|
||||
connect_clicked (W.bpb_i, W.blank_password_button,
|
||||
[&W, &P] { P.prefs.database_password = "";
|
||||
W.get_database_connection (P.prefs); });
|
||||
connect_clicked (W.cpb_i, W.create_password_button,
|
||||
[&W, &P]
|
||||
{ do_set_password_on_database (P);
|
||||
W.get_database_connection (P.prefs); });
|
||||
P.password.set_sensitive (1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static tuple <Prefs, unique_ptr<DB>> get_root_access (Prefs P)
|
||||
{
|
||||
Prefs root_P {P};
|
||||
root_P.database_user = "root";
|
||||
root_P.database_instance = "";
|
||||
|
||||
try { if (P.root_password.length ())
|
||||
{
|
||||
root_P.database_password = P.root_password;
|
||||
return {move (P), make_unique<DB> (root_P)};
|
||||
} }
|
||||
catch (exception&) {}
|
||||
|
||||
root_P.database_password = P.database_password;
|
||||
|
||||
try { P.root_password = P.database_password;
|
||||
P.database_password = {};
|
||||
return {move (P), make_unique<DB> (root_P)}; }
|
||||
catch (exception&) {}
|
||||
|
||||
root_P.database_password = {};
|
||||
|
||||
try { P.root_password = {};
|
||||
return {move (P), make_unique<DB> (root_P)}; }
|
||||
catch (exception& E) { cerr << "XXX: " << E.what () << "\n"; }
|
||||
|
||||
return {move (P), unique_ptr<DB> {}};
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void create_database_as_user (Database_Prefs& P)
|
||||
{
|
||||
Preferences prefs {P.prefs};
|
||||
prefs.database_instance = "";
|
||||
DB db {prefs};
|
||||
auto I {db.instruction ()};
|
||||
I << "create database " << P.prefs.database_instance
|
||||
<< " character set 'utf8'";
|
||||
I.execute ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void create_database_as_root (Database_Prefs& P)
|
||||
{
|
||||
Prefs prefs {P.prefs};
|
||||
prefs.database_instance = "";
|
||||
auto [p, root_db] {get_root_access (prefs)};
|
||||
P.prefs.root_password = p.root_password;
|
||||
if (! root_db) create_database_as_user (P);
|
||||
root_db->instruction ("create database " + P.prefs.database_instance
|
||||
+ " character set 'utf8'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool database_instance_problem (Wizard& W, Database_Prefs& P)
|
||||
{
|
||||
Prefs prefs {P.prefs};
|
||||
prefs.database_instance = "";
|
||||
auto [p, root_db] {get_root_access (prefs)};
|
||||
prefs.root_password = p.root_password;
|
||||
|
||||
if (root_db)
|
||||
{
|
||||
P.prefs.root_password = prefs.root_password;
|
||||
if (root_db->scalar_result<int> (0,
|
||||
"select count(*) "
|
||||
"from information_schema.schemata "
|
||||
"where schema_name='%s'",
|
||||
P.prefs.database_instance.data ()))
|
||||
return 1;
|
||||
if (W.force) { create_database_as_root (P); return 1; }
|
||||
W.create_database_panel->show_all ();
|
||||
connect_clicked (W.cdb_i, W.create_database_button,
|
||||
[&W, &P] { create_database_as_root (P); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DB user_db {prefs};
|
||||
if (user_db.scalar_result<int> (0,
|
||||
"select count(*) "
|
||||
"from information_schema.schemata "
|
||||
"where schema_name='%s'",
|
||||
P.prefs.database_instance.data ()))
|
||||
return 1;
|
||||
if (W.force)
|
||||
{
|
||||
try { create_database_as_user (P); return 1; }
|
||||
catch (exception&)
|
||||
{
|
||||
W.privilege_panel->show_all ();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
W.create_database_panel->show_all ();
|
||||
connect_clicked (W.cdb_i, W.create_database_button,
|
||||
[&W, &P] { create_database_as_user (P); });
|
||||
}
|
||||
catch (exception& E) {}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
inline void grant_privileges
|
||||
(DB& root_db, const string& database, const string& user)
|
||||
{ root_db.instruction ("grant all on " + database + ".* to " + user); }
|
||||
|
||||
static void grant_privileges (DB& root_db, Wizard& W)
|
||||
{
|
||||
const auto& P {W.database_prefs.prefs};
|
||||
grant_privileges (root_db, P.database_instance, P.database_user);
|
||||
}
|
||||
|
||||
static void grant_privileges (Wizard& W)
|
||||
{
|
||||
auto [prefs, root_db] {get_root_access (W.database_prefs.prefs)};
|
||||
if (root_db) { W.privilege_panel->hide ();
|
||||
return grant_privileges (*root_db, W); }
|
||||
W.privilege_panel->show_all ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool no_user_access (Wizard& W, const string& error)
|
||||
{
|
||||
static const regex error_re {".*access denied for user.*", regex::icase};
|
||||
|
||||
if (! regex_match (error, error_re)) return 1;
|
||||
|
||||
{Prefs p {W.database_prefs.prefs};
|
||||
p.database_instance = "";
|
||||
try { DB {p}; grant_privileges (W); return 1; }
|
||||
catch (exception&) {}
|
||||
}
|
||||
|
||||
auto [p, root_db] {get_root_access (W.database_prefs.prefs)};
|
||||
W.database_prefs = p;
|
||||
|
||||
if (! root_db)
|
||||
{
|
||||
W.cannot_access_panel_1->show_all ();
|
||||
W.database_prefs.password.set_sensitive (1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (root_db->scalar_result<int>
|
||||
(0,
|
||||
"select count(*) from mysql.user where User='%s'",
|
||||
W.database_prefs.prefs.database_user))
|
||||
{
|
||||
W.cannot_access_panel_2->show_all ();
|
||||
W.database_prefs.password.set_sensitive (1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {auto I {root_db->instruction ()};
|
||||
I << "create user '"
|
||||
<< W.database_prefs.prefs.database_user
|
||||
<< "'";
|
||||
if (W.database_prefs.prefs.database_password.length ())
|
||||
I << " identified by '"
|
||||
<< W.database_prefs.prefs.database_password << "'";
|
||||
I.execute ();
|
||||
}
|
||||
/* Putting in user names containing a hypen produces errors,
|
||||
* but the user is correctly constructed nonetheless. */
|
||||
catch (exception&) {}
|
||||
|
||||
grant_privileges (*root_db, W);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void generate_random_password (Database_Prefs& P)
|
||||
{
|
||||
static mt19937 E;
|
||||
static uniform_int_distribution I {'a', 'z'};
|
||||
string new_password;
|
||||
for (int i = 0; i < 20; ++i) new_password += I (E);
|
||||
Preferences temp_P {P.prefs};
|
||||
temp_P.database_instance = "";
|
||||
DB {temp_P}
|
||||
.instruction ("set password=password('" + new_password + "')");
|
||||
P.prefs.database_password = new_password;
|
||||
}
|
||||
|
||||
static bool blank_password_problem (Wizard& W, Database_Prefs& P)
|
||||
{
|
||||
bool complete {0};
|
||||
if (! P.prefs.blank_password_forced)
|
||||
{
|
||||
if (W.force) { generate_random_password (P);
|
||||
return 1; }
|
||||
W.no_password_panel->show_all ();
|
||||
connect_clicked (W.gpb_i, W.generate_password_button,
|
||||
[&W, &P]
|
||||
{ generate_random_password (P);
|
||||
W.get_database_connection (P.prefs); });
|
||||
connect_clicked (W.unpb_i, W.use_no_password_button,
|
||||
[&W, &P]
|
||||
{ P.prefs.blank_password_forced = 1;
|
||||
W.get_database_connection (P.prefs); });
|
||||
}
|
||||
|
||||
else /* Blank password forced. */
|
||||
{
|
||||
W.blank_password_panel->show_all ();
|
||||
W.blank_password_ack_button.set_active ();
|
||||
W.bpab_i.disconnect ();
|
||||
W.bpab_i = W.blank_password_ack_button
|
||||
.signal_toggled ().connect ([&W, &P]
|
||||
{ P.prefs.blank_password_forced = 0;
|
||||
W.get_database_connection (P.prefs); });
|
||||
complete = 1;
|
||||
}
|
||||
|
||||
P.password.set_sensitive (1);
|
||||
return complete;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Wizard::get_database_connection (Prefs P)
|
||||
{
|
||||
no_password_panel->hide ();
|
||||
blank_password_panel->hide ();
|
||||
cannot_access_panel_1->hide ();
|
||||
cannot_access_panel_2->hide ();
|
||||
privilege_panel->hide ();
|
||||
create_database_panel->hide ();
|
||||
create_password_panel->hide ();
|
||||
port_problem_panel->hide ();
|
||||
socket_problem_panel->hide ();
|
||||
host_problem_panel->hide ();
|
||||
|
||||
database_prefs = P;
|
||||
database_prefs.password.set_sensitive (0);
|
||||
database_prefs.submit.set_sensitive (0);
|
||||
set_page_complete (database_access_page, 0);
|
||||
bool complete {1};
|
||||
|
||||
try { DB db {P}; }
|
||||
catch (exception& E)
|
||||
{
|
||||
cerr << "Database response: " << E.what () << "\n";
|
||||
|
||||
if (host_problem (*this, E.what ())
|
||||
|| socket_problem (*this, E.what ())
|
||||
|| port_problem (*this, E.what ()))
|
||||
return;
|
||||
|
||||
if (P.database_password.empty ())
|
||||
complete = no_user_access (*this, E.what ()) && complete;
|
||||
|
||||
else
|
||||
{
|
||||
string original_password {""};
|
||||
swap (P.database_password, original_password);
|
||||
|
||||
try { { DB db {P}; }
|
||||
swap (P.database_password, original_password);
|
||||
complete
|
||||
= set_password_on_database (*this, database_prefs)
|
||||
&& complete; }
|
||||
catch (exception& E)
|
||||
{
|
||||
swap (P.database_password, original_password);
|
||||
complete = no_user_access (*this, E.what ()) && complete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (database_prefs.prefs.database_password.empty () && complete)
|
||||
complete = blank_password_problem (*this, database_prefs);
|
||||
|
||||
complete = database_instance_problem (*this, database_prefs)
|
||||
&& complete;
|
||||
|
||||
set_page_complete (database_access_page, complete);
|
||||
if (force && complete) next_page ();
|
||||
}
|
||||
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
145
trader-desk/wizard.h
Normal file
145
trader-desk/wizard.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright (c) 2017, 2020 Dale Mellor
|
||||
*
|
||||
* This file is part of the trader-desk package.
|
||||
*
|
||||
* The trader-desk package is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* The trader-desk package is distributed in the hope that it will be
|
||||
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see http://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DMBCS__TRADER_DESK__WIZARD__H
|
||||
#define DMBCS__TRADER_DESK__WIZARD__H
|
||||
|
||||
|
||||
#include <gtkmm.h>
|
||||
#include <trader-desk/auto-config.h>
|
||||
#include <trader-desk/preferences.h>
|
||||
#include <atomic>
|
||||
|
||||
|
||||
namespace DMBCS::Trader_Desk {
|
||||
|
||||
|
||||
|
||||
#define t_ pgettext
|
||||
|
||||
|
||||
|
||||
struct Prefs : Preferences { bool non_default_config_file {0};
|
||||
bool blank_password_forced {0};
|
||||
string root_password {}; };
|
||||
|
||||
|
||||
|
||||
struct Database_Prefs : Gtk::Grid
|
||||
{
|
||||
Prefs prefs;
|
||||
|
||||
Gtk::Entry host;
|
||||
Gtk::Entry socket;
|
||||
Gtk::Entry port;
|
||||
Gtk::Entry database;
|
||||
Gtk::Entry user;
|
||||
Gtk::Entry password;
|
||||
Gtk::Button submit {t_("Label", "Commit change")};
|
||||
|
||||
Database_Prefs ();
|
||||
Database_Prefs& operator= (const Prefs& P);
|
||||
Prefs update_prefs (Prefs P);
|
||||
Prefs update_prefs () { return prefs = update_prefs (prefs); }
|
||||
};
|
||||
|
||||
|
||||
|
||||
struct Wizard : Gtk::Assistant
|
||||
{
|
||||
/* The actual Preferences which we are checking/generating are held
|
||||
* inside the database_prefs object below. */
|
||||
|
||||
bool force;
|
||||
Gtk::Widget* last_page {0};
|
||||
|
||||
Gtk::VBox configuration_page;
|
||||
Gtk::Entry config_file_entry;
|
||||
Gtk::Button use_default_config_button {t_("Label", "Use _default"), 1};
|
||||
sigc::connection udcb_i;
|
||||
Gtk::Button find_file_button {t_("Label", "_Find file"), 1};
|
||||
sigc::connection ffb_i;
|
||||
Gtk::Button change_button {t_("Label", "Change")};
|
||||
sigc::connection cb_i;
|
||||
unique_ptr<Gtk::Widget> create_file_panel;
|
||||
Gtk::Button create_file_button {t_("Label", "Create file")};
|
||||
sigc::connection cfb_i;
|
||||
unique_ptr<Gtk::Widget> non_default_panel;
|
||||
Gtk::CheckButton acknowledge_check {t_("Label", "_Acknowledge"), 1};
|
||||
sigc::connection ac_i;
|
||||
|
||||
|
||||
Gtk::VBox database_access_page;
|
||||
Database_Prefs database_prefs;
|
||||
unique_ptr<Gtk::Widget> no_password_panel;
|
||||
Gtk::Button generate_password_button
|
||||
{t_("Label", "_Generate password"), 1};
|
||||
sigc::connection gpb_i;
|
||||
Gtk::Button use_no_password_button
|
||||
{t_("Label", "_Use no password"), 1};
|
||||
sigc::connection unpb_i;
|
||||
unique_ptr<Gtk::Widget> blank_password_panel;
|
||||
Gtk::CheckButton blank_password_ack_button
|
||||
{t_("Label", "_Acknowledged"), 1};
|
||||
sigc::connection bpab_i;
|
||||
unique_ptr<Gtk::Widget> cannot_access_panel_1;
|
||||
unique_ptr<Gtk::Widget> cannot_access_panel_2;
|
||||
unique_ptr<Gtk::Widget> privilege_panel;
|
||||
Gtk::Entry database_root_entry;
|
||||
unique_ptr<Gtk::Widget> create_database_panel;
|
||||
Gtk::Button create_database_button
|
||||
{t_("Label", "Create database"), 1};
|
||||
sigc::connection cdb_i;
|
||||
unique_ptr<Gtk::Widget> create_password_panel;
|
||||
Gtk::Button create_password_button
|
||||
{t_("Label", "Yes, set password")};
|
||||
sigc::connection cpb_i;
|
||||
Gtk::Button blank_password_button
|
||||
{t_("Label", "No, use blank password")};
|
||||
sigc::connection bpb_i;
|
||||
unique_ptr<Gtk::Widget> port_problem_panel;
|
||||
unique_ptr<Gtk::Widget> socket_problem_panel;
|
||||
unique_ptr<Gtk::Widget> host_problem_panel;
|
||||
|
||||
Gtk::VBox tables_page;
|
||||
|
||||
Gtk::VBox data_page;
|
||||
Gtk::ProgressBar progress_bar;
|
||||
atomic<int> progress_total;
|
||||
atomic<int> progress_count;
|
||||
unique_ptr<Gtk::Widget> database_ready_panel;
|
||||
unique_ptr<Gtk::Widget> no_data_panel;
|
||||
|
||||
|
||||
|
||||
int page_order (Gtk::Widget *const, Gtk::Widget *const);
|
||||
|
||||
sigc::signal<void, Preferences> signal_wizard_complete;
|
||||
|
||||
Wizard (const string& config_file_name, const bool force);
|
||||
|
||||
void get_database_connection (Prefs);
|
||||
};
|
||||
|
||||
|
||||
} /* End of namespace DMBCS::Trader_Desk. */
|
||||
|
||||
|
||||
#endif /* Undefined DMBCS__TRADER_DESK__WIZARD__H. */
|
||||
Loading…
Add table
Add a link
Reference in a new issue