Init
This commit is contained in:
		
							
								
								
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
*~
 | 
			
		||||
*.swp
 | 
			
		||||
tarballs/
 | 
			
		||||
.stack-work/
 | 
			
		||||
imgui.ini
 | 
			
		||||
settings.json
 | 
			
		||||
*.lock
 | 
			
		||||
tags
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
[submodule "deps/dear-implot.hs"]
 | 
			
		||||
	path = deps/dear-implot.hs
 | 
			
		||||
	url = https://github.com/Drezil/dear-implot.hs
 | 
			
		||||
[submodule "deps/dear-imgui.hs"]
 | 
			
		||||
	path = deps/dear-imgui.hs
 | 
			
		||||
	url = https://github.com/haskell-game/dear-imgui.hs
 | 
			
		||||
							
								
								
									
										0
									
								
								.hlint.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								.hlint.yaml
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										3
									
								
								ChangeLog.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								ChangeLog.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
# Changelog for ibhelper
 | 
			
		||||
 | 
			
		||||
## Unreleased changes
 | 
			
		||||
							
								
								
									
										30
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
Copyright Stefan Dresselhaus (c) 2019
 | 
			
		||||
 | 
			
		||||
All rights reserved.
 | 
			
		||||
 | 
			
		||||
Redistribution and use in source and binary forms, with or without
 | 
			
		||||
modification, are permitted provided that the following conditions are met:
 | 
			
		||||
 | 
			
		||||
    * Redistributions of source code must retain the above copyright
 | 
			
		||||
      notice, this list of conditions and the following disclaimer.
 | 
			
		||||
 | 
			
		||||
    * Redistributions in binary form must reproduce the above
 | 
			
		||||
      copyright notice, this list of conditions and the following
 | 
			
		||||
      disclaimer in the documentation and/or other materials provided
 | 
			
		||||
      with the distribution.
 | 
			
		||||
 | 
			
		||||
    * Neither the name of Author name here nor the names of other
 | 
			
		||||
      contributors may be used to endorse or promote products derived
 | 
			
		||||
      from this software without specific prior written permission.
 | 
			
		||||
 | 
			
		||||
THIS SOFTWARE IS PROVIDED BY THE 2022 Stefan Dresselhaus HOLDERS AND CONTRIBUTORS
 | 
			
		||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 | 
			
		||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 2022 Stefan Dresselhaus
 | 
			
		||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
			
		||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 | 
			
		||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 | 
			
		||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 | 
			
		||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | 
			
		||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 | 
			
		||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
# ibhelper
 | 
			
		||||
 | 
			
		||||
## Execute  
 | 
			
		||||
 | 
			
		||||
* Run `stack exec -- ibhelper-exe` to see "We're inside the application!"
 | 
			
		||||
* With `stack exec -- ibhelper-exe --verbose` you will see the same message, with more logging.
 | 
			
		||||
 | 
			
		||||
## Run tests
 | 
			
		||||
 | 
			
		||||
`stack test`
 | 
			
		||||
							
								
								
									
										108
									
								
								app/Main.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								app/Main.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,108 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
{-# LANGUAGE TemplateHaskell #-}
 | 
			
		||||
module Main (main) where
 | 
			
		||||
 | 
			
		||||
import Import
 | 
			
		||||
import Data.Aeson (eitherDecodeFileStrict')
 | 
			
		||||
import Control.Monad.Managed
 | 
			
		||||
import Control.Concurrent
 | 
			
		||||
import DearImGui
 | 
			
		||||
import DearImGui.OpenGL3
 | 
			
		||||
import DearImGui.GLFW
 | 
			
		||||
import DearImGui.GLFW.OpenGL
 | 
			
		||||
import Run
 | 
			
		||||
import RIO.Process
 | 
			
		||||
import System.Directory
 | 
			
		||||
import Options.Applicative.Simple
 | 
			
		||||
import qualified Paths_ibhelper
 | 
			
		||||
import qualified Graphics.UI.GLFW as GLFW
 | 
			
		||||
import qualified Data.Text as T
 | 
			
		||||
import Prelude (putStrLn)
 | 
			
		||||
import AppFiller
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
main :: IO ()
 | 
			
		||||
main = do
 | 
			
		||||
  (options, ()) <- simpleOptions
 | 
			
		||||
    $(simpleVersion Paths_ibhelper.version)
 | 
			
		||||
    "Header for command line arguments"
 | 
			
		||||
    "Program description, also for command line arguments"
 | 
			
		||||
    (Options
 | 
			
		||||
       <$> switch ( long "verbose"
 | 
			
		||||
                 <> short 'v'
 | 
			
		||||
                 <> help "Verbose output?"
 | 
			
		||||
                  )
 | 
			
		||||
    )
 | 
			
		||||
    empty
 | 
			
		||||
  settingsFileExists <- doesFileExist "settings.json"
 | 
			
		||||
  settings <- if settingsFileExists
 | 
			
		||||
              then do
 | 
			
		||||
                s <- fmap unDefaultJSON <$> eitherDecodeFileStrict' "settings.json"
 | 
			
		||||
                pPrint s
 | 
			
		||||
                case s of
 | 
			
		||||
                  Left e -> putStrLn ("Error loading settings: \n"<>e) >> return def
 | 
			
		||||
                  Right s' -> return s'
 | 
			
		||||
              else return def
 | 
			
		||||
  lo <- logOptionsHandle stderr (optionsVerbose options)
 | 
			
		||||
    <&> setLogMinLevel (settings ^. logLevel)
 | 
			
		||||
    <&> setLogTerminal True
 | 
			
		||||
  pc <- mkDefaultProcessContext
 | 
			
		||||
  withLogFunc lo $ \lf -> do
 | 
			
		||||
    -- let bare_log = unLogFunc $ view logFuncL lf
 | 
			
		||||
    --     logErr = liftIO . bare_log callStack "" LevelError
 | 
			
		||||
    initialized <- GLFW.init
 | 
			
		||||
    unless initialized $ error "GLFW init failed"
 | 
			
		||||
 | 
			
		||||
    liftIO $ runManaged $ do
 | 
			
		||||
      mwin <- managed $ bracket
 | 
			
		||||
        (GLFW.createWindow (settings ^. windowParams . windowWidth) (settings ^. windowParams . windowHeight) "IB-Helper" Nothing Nothing)
 | 
			
		||||
        (maybe (return ()) GLFW.destroyWindow)
 | 
			
		||||
      case mwin of
 | 
			
		||||
        Just win -> do
 | 
			
		||||
          liftIO $ do
 | 
			
		||||
            GLFW.makeContextCurrent (Just win)
 | 
			
		||||
            GLFW.swapInterval 1
 | 
			
		||||
 | 
			
		||||
          -- Create an ImGui context
 | 
			
		||||
          _ <- managed $ bracket createContext destroyContext
 | 
			
		||||
 | 
			
		||||
          -- Create an ImPlot context
 | 
			
		||||
          -- _ <- managed $ bracket createPlotContext destroyPlotContext
 | 
			
		||||
 | 
			
		||||
          -- Initialize ImGui's GLFW backend
 | 
			
		||||
          _ <- managed_ $ bracket_ (glfwInitForOpenGL win True) glfwShutdown
 | 
			
		||||
 | 
			
		||||
          -- Initialize ImGui's OpenGL backend
 | 
			
		||||
          _ <- managed_ $ bracket_ openGL3Init openGL3Shutdown
 | 
			
		||||
 | 
			
		||||
          twsConnectionRefsHost <- liftIO $ atomically $ newTVar $ settings ^. twsConnection . host . to T.unpack
 | 
			
		||||
          twsConnectionRefsPort <- liftIO $ atomically $ newTVar $ settings ^. twsConnection . port . to T.unpack
 | 
			
		||||
          twsConnectionStatus   <- liftIO $ atomically $ newTVar $ TWSDisconnected
 | 
			
		||||
          twsConnectionSend     <- liftIO $ atomically $ newTQueue
 | 
			
		||||
          twsConnectionRecieve  <- liftIO $ atomically $ newTQueue
 | 
			
		||||
          let twsConnectionRefs = TWSConnectionRefs{..}
 | 
			
		||||
          liftIO $ atomically $ writeTQueue twsConnectionSend $ Msg_IB_OUT $ IB_RequestMarketDataType DelayedFrozen
 | 
			
		||||
          currentAccount        <- liftIO $ newTVarIO $ Nothing
 | 
			
		||||
          tickerIdToSymbol      <- liftIO $ newTVarIO $ mempty
 | 
			
		||||
          appCharts <- liftIO $ newTVarIO $ mempty
 | 
			
		||||
          appData <- liftIO $ DataRefs
 | 
			
		||||
                          <$> newTVarIO mempty
 | 
			
		||||
                          <*> newTVarIO Nothing
 | 
			
		||||
                          <*> newTVarIO mempty
 | 
			
		||||
                          <*> newTVarIO mempty
 | 
			
		||||
          let app = App
 | 
			
		||||
               { appSettings       = settings
 | 
			
		||||
               , appLogFunc        = lf
 | 
			
		||||
               , appProcessContext = pc
 | 
			
		||||
               , appOptions        = options
 | 
			
		||||
               , appWindow         = win
 | 
			
		||||
               , appRefs           = AppRefs{..}
 | 
			
		||||
               , appData           = appData
 | 
			
		||||
               }
 | 
			
		||||
          void $ liftIO $ forkIO $ appFiller app
 | 
			
		||||
          liftIO $ runRIO app run
 | 
			
		||||
        Nothing -> do
 | 
			
		||||
          error "GLFW createWindow failed"
 | 
			
		||||
 | 
			
		||||
  GLFW.terminate
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								deps/dear-imgui.hs
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								deps/dear-imgui.hs
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule deps/dear-imgui.hs added at e5969f6b35
									
								
							
							
								
								
									
										1
									
								
								deps/dear-implot.hs
									
									
									
									
										vendored
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
									
										1
									
								
								deps/dear-implot.hs
									
									
									
									
										vendored
									
									
										Submodule
									
								
							 Submodule deps/dear-implot.hs added at 78f7df091c
									
								
							
							
								
								
									
										248
									
								
								ibhelper.cabal
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								ibhelper.cabal
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,248 @@
 | 
			
		||||
cabal-version: 1.12
 | 
			
		||||
 | 
			
		||||
-- This file has been generated from package.yaml by hpack version 0.34.4.
 | 
			
		||||
--
 | 
			
		||||
-- see: https://github.com/sol/hpack
 | 
			
		||||
 | 
			
		||||
name:           ibhelper
 | 
			
		||||
version:        0.1.0.0
 | 
			
		||||
description:    Please see the README.md
 | 
			
		||||
homepage:       https://github.com/Drezil/ibhelper#readme
 | 
			
		||||
bug-reports:    https://github.com/Drezil/ibhelper/issues
 | 
			
		||||
author:         Stefan Dresselhaus
 | 
			
		||||
maintainer:     sdressel@pwning.de
 | 
			
		||||
copyright:      2022 Stefan Dresselhaus
 | 
			
		||||
license:        BSD3
 | 
			
		||||
license-file:   LICENSE
 | 
			
		||||
build-type:     Simple
 | 
			
		||||
extra-source-files:
 | 
			
		||||
    README.md
 | 
			
		||||
    ChangeLog.md
 | 
			
		||||
 | 
			
		||||
source-repository head
 | 
			
		||||
  type: git
 | 
			
		||||
  location: https://github.com/Drezil/ibhelper
 | 
			
		||||
 | 
			
		||||
library
 | 
			
		||||
  exposed-modules:
 | 
			
		||||
      AppFiller
 | 
			
		||||
      Chart
 | 
			
		||||
      IBClient.Connection
 | 
			
		||||
      IBClient.Types
 | 
			
		||||
      Import
 | 
			
		||||
      Run
 | 
			
		||||
      Types
 | 
			
		||||
      Util
 | 
			
		||||
  other-modules:
 | 
			
		||||
      Paths_ibhelper
 | 
			
		||||
  hs-source-dirs:
 | 
			
		||||
      src
 | 
			
		||||
  default-extensions:
 | 
			
		||||
      BangPatterns
 | 
			
		||||
      BinaryLiterals
 | 
			
		||||
      ConstraintKinds
 | 
			
		||||
      DataKinds
 | 
			
		||||
      DefaultSignatures
 | 
			
		||||
      DeriveDataTypeable
 | 
			
		||||
      DeriveFoldable
 | 
			
		||||
      DeriveFunctor
 | 
			
		||||
      DeriveGeneric
 | 
			
		||||
      DeriveTraversable
 | 
			
		||||
      DoAndIfThenElse
 | 
			
		||||
      EmptyDataDecls
 | 
			
		||||
      ExistentialQuantification
 | 
			
		||||
      FlexibleContexts
 | 
			
		||||
      FlexibleInstances
 | 
			
		||||
      FunctionalDependencies
 | 
			
		||||
      GADTs
 | 
			
		||||
      GeneralizedNewtypeDeriving
 | 
			
		||||
      InstanceSigs
 | 
			
		||||
      KindSignatures
 | 
			
		||||
      LambdaCase
 | 
			
		||||
      MultiParamTypeClasses
 | 
			
		||||
      MultiWayIf
 | 
			
		||||
      NamedFieldPuns
 | 
			
		||||
      NoImplicitPrelude
 | 
			
		||||
      OverloadedStrings
 | 
			
		||||
      PartialTypeSignatures
 | 
			
		||||
      PatternGuards
 | 
			
		||||
      PolyKinds
 | 
			
		||||
      RankNTypes
 | 
			
		||||
      RecordWildCards
 | 
			
		||||
      ScopedTypeVariables
 | 
			
		||||
      StandaloneDeriving
 | 
			
		||||
      TupleSections
 | 
			
		||||
      TypeFamilies
 | 
			
		||||
      TypeSynonymInstances
 | 
			
		||||
      ViewPatterns
 | 
			
		||||
      DuplicateRecordFields
 | 
			
		||||
  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints
 | 
			
		||||
  build-depends:
 | 
			
		||||
      GLFW-b
 | 
			
		||||
    , StateVar
 | 
			
		||||
    , aeson
 | 
			
		||||
    , base >=4.11 && <10
 | 
			
		||||
    , binary
 | 
			
		||||
    , bytestring
 | 
			
		||||
    , data-default
 | 
			
		||||
    , dear-imgui
 | 
			
		||||
    , directory
 | 
			
		||||
    , fingertree
 | 
			
		||||
    , gl
 | 
			
		||||
    , managed
 | 
			
		||||
    , microlens-th
 | 
			
		||||
    , network
 | 
			
		||||
    , pretty-show
 | 
			
		||||
    , rio >=0.1.12.0
 | 
			
		||||
    , stm
 | 
			
		||||
    , text
 | 
			
		||||
    , time
 | 
			
		||||
    , type-iso
 | 
			
		||||
    , unordered-containers
 | 
			
		||||
  default-language: Haskell2010
 | 
			
		||||
 | 
			
		||||
executable ibhelper-exe
 | 
			
		||||
  main-is: Main.hs
 | 
			
		||||
  other-modules:
 | 
			
		||||
      Paths_ibhelper
 | 
			
		||||
  hs-source-dirs:
 | 
			
		||||
      app
 | 
			
		||||
  default-extensions:
 | 
			
		||||
      BangPatterns
 | 
			
		||||
      BinaryLiterals
 | 
			
		||||
      ConstraintKinds
 | 
			
		||||
      DataKinds
 | 
			
		||||
      DefaultSignatures
 | 
			
		||||
      DeriveDataTypeable
 | 
			
		||||
      DeriveFoldable
 | 
			
		||||
      DeriveFunctor
 | 
			
		||||
      DeriveGeneric
 | 
			
		||||
      DeriveTraversable
 | 
			
		||||
      DoAndIfThenElse
 | 
			
		||||
      EmptyDataDecls
 | 
			
		||||
      ExistentialQuantification
 | 
			
		||||
      FlexibleContexts
 | 
			
		||||
      FlexibleInstances
 | 
			
		||||
      FunctionalDependencies
 | 
			
		||||
      GADTs
 | 
			
		||||
      GeneralizedNewtypeDeriving
 | 
			
		||||
      InstanceSigs
 | 
			
		||||
      KindSignatures
 | 
			
		||||
      LambdaCase
 | 
			
		||||
      MultiParamTypeClasses
 | 
			
		||||
      MultiWayIf
 | 
			
		||||
      NamedFieldPuns
 | 
			
		||||
      NoImplicitPrelude
 | 
			
		||||
      OverloadedStrings
 | 
			
		||||
      PartialTypeSignatures
 | 
			
		||||
      PatternGuards
 | 
			
		||||
      PolyKinds
 | 
			
		||||
      RankNTypes
 | 
			
		||||
      RecordWildCards
 | 
			
		||||
      ScopedTypeVariables
 | 
			
		||||
      StandaloneDeriving
 | 
			
		||||
      TupleSections
 | 
			
		||||
      TypeFamilies
 | 
			
		||||
      TypeSynonymInstances
 | 
			
		||||
      ViewPatterns
 | 
			
		||||
      DuplicateRecordFields
 | 
			
		||||
  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
 | 
			
		||||
  build-depends:
 | 
			
		||||
      GLFW-b
 | 
			
		||||
    , StateVar
 | 
			
		||||
    , aeson
 | 
			
		||||
    , base >=4.11 && <10
 | 
			
		||||
    , binary
 | 
			
		||||
    , bytestring
 | 
			
		||||
    , data-default
 | 
			
		||||
    , dear-imgui
 | 
			
		||||
    , directory
 | 
			
		||||
    , fingertree
 | 
			
		||||
    , gl
 | 
			
		||||
    , ibhelper
 | 
			
		||||
    , managed
 | 
			
		||||
    , microlens-th
 | 
			
		||||
    , network
 | 
			
		||||
    , optparse-simple
 | 
			
		||||
    , pretty-show
 | 
			
		||||
    , rio >=0.1.12.0
 | 
			
		||||
    , stm
 | 
			
		||||
    , text
 | 
			
		||||
    , time
 | 
			
		||||
    , type-iso
 | 
			
		||||
    , unordered-containers
 | 
			
		||||
  default-language: Haskell2010
 | 
			
		||||
 | 
			
		||||
test-suite ibhelper-test
 | 
			
		||||
  type: exitcode-stdio-1.0
 | 
			
		||||
  main-is: Spec.hs
 | 
			
		||||
  other-modules:
 | 
			
		||||
      UtilSpec
 | 
			
		||||
      Paths_ibhelper
 | 
			
		||||
  hs-source-dirs:
 | 
			
		||||
      test
 | 
			
		||||
  default-extensions:
 | 
			
		||||
      BangPatterns
 | 
			
		||||
      BinaryLiterals
 | 
			
		||||
      ConstraintKinds
 | 
			
		||||
      DataKinds
 | 
			
		||||
      DefaultSignatures
 | 
			
		||||
      DeriveDataTypeable
 | 
			
		||||
      DeriveFoldable
 | 
			
		||||
      DeriveFunctor
 | 
			
		||||
      DeriveGeneric
 | 
			
		||||
      DeriveTraversable
 | 
			
		||||
      DoAndIfThenElse
 | 
			
		||||
      EmptyDataDecls
 | 
			
		||||
      ExistentialQuantification
 | 
			
		||||
      FlexibleContexts
 | 
			
		||||
      FlexibleInstances
 | 
			
		||||
      FunctionalDependencies
 | 
			
		||||
      GADTs
 | 
			
		||||
      GeneralizedNewtypeDeriving
 | 
			
		||||
      InstanceSigs
 | 
			
		||||
      KindSignatures
 | 
			
		||||
      LambdaCase
 | 
			
		||||
      MultiParamTypeClasses
 | 
			
		||||
      MultiWayIf
 | 
			
		||||
      NamedFieldPuns
 | 
			
		||||
      NoImplicitPrelude
 | 
			
		||||
      OverloadedStrings
 | 
			
		||||
      PartialTypeSignatures
 | 
			
		||||
      PatternGuards
 | 
			
		||||
      PolyKinds
 | 
			
		||||
      RankNTypes
 | 
			
		||||
      RecordWildCards
 | 
			
		||||
      ScopedTypeVariables
 | 
			
		||||
      StandaloneDeriving
 | 
			
		||||
      TupleSections
 | 
			
		||||
      TypeFamilies
 | 
			
		||||
      TypeSynonymInstances
 | 
			
		||||
      ViewPatterns
 | 
			
		||||
      DuplicateRecordFields
 | 
			
		||||
  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
 | 
			
		||||
  build-depends:
 | 
			
		||||
      GLFW-b
 | 
			
		||||
    , StateVar
 | 
			
		||||
    , aeson
 | 
			
		||||
    , base >=4.11 && <10
 | 
			
		||||
    , binary
 | 
			
		||||
    , bytestring
 | 
			
		||||
    , data-default
 | 
			
		||||
    , dear-imgui
 | 
			
		||||
    , directory
 | 
			
		||||
    , fingertree
 | 
			
		||||
    , gl
 | 
			
		||||
    , hspec
 | 
			
		||||
    , ibhelper
 | 
			
		||||
    , managed
 | 
			
		||||
    , microlens-th
 | 
			
		||||
    , network
 | 
			
		||||
    , pretty-show
 | 
			
		||||
    , rio >=0.1.12.0
 | 
			
		||||
    , stm
 | 
			
		||||
    , text
 | 
			
		||||
    , time
 | 
			
		||||
    , type-iso
 | 
			
		||||
    , unordered-containers
 | 
			
		||||
  default-language: Haskell2010
 | 
			
		||||
							
								
								
									
										121
									
								
								package.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								package.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
name:                ibhelper
 | 
			
		||||
version:             0.1.0.0
 | 
			
		||||
github:              Drezil/ibhelper
 | 
			
		||||
license:             BSD3
 | 
			
		||||
author:              Stefan Dresselhaus
 | 
			
		||||
maintainer:          sdressel@pwning.de
 | 
			
		||||
copyright:           2022 Stefan Dresselhaus
 | 
			
		||||
 | 
			
		||||
extra-source-files:
 | 
			
		||||
- README.md
 | 
			
		||||
- ChangeLog.md
 | 
			
		||||
 | 
			
		||||
# Metadata used when publishing your package
 | 
			
		||||
# synopsis:            Short description of your package
 | 
			
		||||
# category:            Web
 | 
			
		||||
 | 
			
		||||
# To avoid duplicated efforts in documentation and dealing with the
 | 
			
		||||
# complications of embedding Haddock markup inside cabal files, it is
 | 
			
		||||
# common to point users to the README.md file.
 | 
			
		||||
description:         Please see the README.md
 | 
			
		||||
 | 
			
		||||
default-extensions:
 | 
			
		||||
  - BangPatterns
 | 
			
		||||
  - BinaryLiterals
 | 
			
		||||
  - ConstraintKinds
 | 
			
		||||
  - DataKinds
 | 
			
		||||
  - DefaultSignatures
 | 
			
		||||
  - DeriveDataTypeable
 | 
			
		||||
  - DeriveFoldable
 | 
			
		||||
  - DeriveFunctor
 | 
			
		||||
  - DeriveGeneric
 | 
			
		||||
  - DeriveTraversable
 | 
			
		||||
  - DoAndIfThenElse
 | 
			
		||||
  - EmptyDataDecls
 | 
			
		||||
  - ExistentialQuantification
 | 
			
		||||
  - FlexibleContexts
 | 
			
		||||
  - FlexibleInstances
 | 
			
		||||
  - FunctionalDependencies
 | 
			
		||||
  - GADTs
 | 
			
		||||
  - GeneralizedNewtypeDeriving
 | 
			
		||||
  - InstanceSigs
 | 
			
		||||
  - KindSignatures
 | 
			
		||||
  - LambdaCase
 | 
			
		||||
  - MultiParamTypeClasses
 | 
			
		||||
  - MultiWayIf
 | 
			
		||||
  - NamedFieldPuns
 | 
			
		||||
  - NoImplicitPrelude
 | 
			
		||||
  - OverloadedStrings
 | 
			
		||||
  - PartialTypeSignatures
 | 
			
		||||
  - PatternGuards
 | 
			
		||||
  - PolyKinds
 | 
			
		||||
  - RankNTypes
 | 
			
		||||
  - RecordWildCards
 | 
			
		||||
  - ScopedTypeVariables
 | 
			
		||||
  - StandaloneDeriving
 | 
			
		||||
  - TupleSections
 | 
			
		||||
  - TypeFamilies
 | 
			
		||||
  - TypeSynonymInstances
 | 
			
		||||
  - ViewPatterns
 | 
			
		||||
  - DuplicateRecordFields
 | 
			
		||||
 | 
			
		||||
dependencies:
 | 
			
		||||
- base >= 4.11 && < 10
 | 
			
		||||
- rio >= 0.1.12.0
 | 
			
		||||
- dear-imgui
 | 
			
		||||
- GLFW-b
 | 
			
		||||
- managed
 | 
			
		||||
- gl
 | 
			
		||||
- aeson
 | 
			
		||||
- data-default
 | 
			
		||||
- directory
 | 
			
		||||
- microlens-th
 | 
			
		||||
- network
 | 
			
		||||
- bytestring
 | 
			
		||||
- stm
 | 
			
		||||
- text
 | 
			
		||||
- pretty-show
 | 
			
		||||
- StateVar
 | 
			
		||||
- type-iso
 | 
			
		||||
- binary
 | 
			
		||||
- time
 | 
			
		||||
- unordered-containers
 | 
			
		||||
- fingertree
 | 
			
		||||
 | 
			
		||||
ghc-options:
 | 
			
		||||
- -Wall
 | 
			
		||||
- -Wcompat
 | 
			
		||||
- -Widentities
 | 
			
		||||
- -Wincomplete-record-updates
 | 
			
		||||
- -Wincomplete-uni-patterns
 | 
			
		||||
- -Wpartial-fields
 | 
			
		||||
- -Wredundant-constraints
 | 
			
		||||
 | 
			
		||||
library:
 | 
			
		||||
  source-dirs: src
 | 
			
		||||
 | 
			
		||||
executables:
 | 
			
		||||
  ibhelper-exe:
 | 
			
		||||
    main:                Main.hs
 | 
			
		||||
    source-dirs:         app
 | 
			
		||||
    dependencies:
 | 
			
		||||
    - ibhelper
 | 
			
		||||
    - optparse-simple
 | 
			
		||||
 | 
			
		||||
    ghc-options:
 | 
			
		||||
    - -threaded
 | 
			
		||||
    - -rtsopts
 | 
			
		||||
    - -with-rtsopts=-N
 | 
			
		||||
 | 
			
		||||
tests:
 | 
			
		||||
  ibhelper-test:
 | 
			
		||||
    main:                Spec.hs
 | 
			
		||||
    source-dirs:         test
 | 
			
		||||
    dependencies:
 | 
			
		||||
    - ibhelper
 | 
			
		||||
    - hspec
 | 
			
		||||
 | 
			
		||||
    ghc-options:
 | 
			
		||||
    - -threaded
 | 
			
		||||
    - -rtsopts
 | 
			
		||||
    - -with-rtsopts=-N
 | 
			
		||||
							
								
								
									
										79
									
								
								src/AppFiller.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/AppFiller.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
{-# LANGUAGE DuplicateRecordFields #-}
 | 
			
		||||
{-# LANGUAGE TypeApplications #-}
 | 
			
		||||
module AppFiller where
 | 
			
		||||
 | 
			
		||||
import Import
 | 
			
		||||
import Types
 | 
			
		||||
 | 
			
		||||
import qualified Data.Text as T
 | 
			
		||||
 | 
			
		||||
import Data.Time
 | 
			
		||||
import Data.FingerTree
 | 
			
		||||
import Data.HashMap.Strict ((!?))
 | 
			
		||||
import qualified Data.HashMap.Strict as HM
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import qualified Debug.Trace as D
 | 
			
		||||
 | 
			
		||||
appFiller :: App -> IO ()
 | 
			
		||||
appFiller app = runRIO app $ withRunInIO $ \run -> do
 | 
			
		||||
  let queue = twsConnectionRecieve . twsConnectionRefs . appRefs $ app
 | 
			
		||||
      debugMsg x = run $ logDebug (display $ T.pack $ "FILLER  : " <> T.unpack x)
 | 
			
		||||
      infoMsg  x = run $ logDebug (display $ T.pack $ "FILLER  : " <> T.unpack x)
 | 
			
		||||
  forever $ do
 | 
			
		||||
    input <- atomically $ readTQueue queue
 | 
			
		||||
    let currentAppData = appData app
 | 
			
		||||
    case input of
 | 
			
		||||
      (Msg_IB_IN IB_PositionData) -> return ()
 | 
			
		||||
      (Msg_IB_IN (IB_ManagedAccts as)) -> do
 | 
			
		||||
        cur <- readTVarIO $ Types.accounts currentAppData
 | 
			
		||||
        actions <- forM as $ \a -> case cur !? a of
 | 
			
		||||
                                                Just _ -> return $ id
 | 
			
		||||
                                                Nothing -> do
 | 
			
		||||
                                                  debugMsg $ "added Account "<> a
 | 
			
		||||
                                                  return $ HM.insertWith const a (mkIBAccount a)
 | 
			
		||||
        atomically $ modifyTVar (Types.accounts currentAppData) $ foldl' (.) id actions
 | 
			
		||||
      (Msg_IB_IN (IB_NextValidID i)) -> atomically $ modifyTVar' (nextValidID currentAppData) (const (Just i))
 | 
			
		||||
      (Msg_IB_IN (IB_ErrorMsg i c m)) -> debugMsg "IB_ErrorMsg not implemented"
 | 
			
		||||
      (Msg_IB_IN (IB_AccountValue k v c n)) -> do
 | 
			
		||||
        let action = HM.update (\ai -> Just $ ai & accountInfo . accountProperties %~ HM.alter (\old -> Just $ (v,c):filter ((/=c) . snd) (fromMaybe [] old)) k) n
 | 
			
		||||
        atomically $ modifyTVar' (Types.accounts currentAppData) action
 | 
			
		||||
      (Msg_IB_IN (IB_AccountUpdateTime t)) -> debugMsg "IB_AccountUpdateTime not implemented"
 | 
			
		||||
      -- (Msg_IB_IN (IB_AccountUpdateTime t)) -> do
 | 
			
		||||
      --   let action = HM.update (\ai -> Just $ ai & accountInfo . accountLastUpdate %~ const t) n
 | 
			
		||||
      --   atomically $ modifyTVar' (Types.accounts currentAppData) action
 | 
			
		||||
      (Msg_IB_IN (IB_PortfolioValue c p mp mv ac u r n)) -> do
 | 
			
		||||
        let cid = conId :: IBContract -> Int
 | 
			
		||||
            updateAction (a@IBPortfolioValue{..}:as)
 | 
			
		||||
             | cid _contract == cid c = IBPortfolioValue c p mp mv ac u r:as
 | 
			
		||||
             | otherwise              = a:updateAction as
 | 
			
		||||
            updateAction [] = [IBPortfolioValue c p mp mv ac u r]
 | 
			
		||||
            action = HM.update (\ai -> Just $ ai & accountPortfolio %~ updateAction) n
 | 
			
		||||
        atomically $ modifyTVar' (Types.accounts currentAppData) action
 | 
			
		||||
      (Msg_IB_IN (IB_SymbolSamples r s)) -> do
 | 
			
		||||
        atomically $ do
 | 
			
		||||
          modifyTVar' (nextValidID currentAppData) (const $ Just r)
 | 
			
		||||
          modifyTVar' (symbolLookupResults currentAppData) (const $ (\IB_SymbolSample{..} -> IBSymbolSample symId symbol secType primaryExchange currency derivatives) <$> s)
 | 
			
		||||
      (Msg_IB_IN t@IB_TickPrice{}) -> run $ handleTickPrice t
 | 
			
		||||
      _ -> --D.trace ("not implemented in AppFiller:" <> show input) $
 | 
			
		||||
           infoMsg $ "not implemented in AppFiller:" <> T.pack (show input)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
handleTickPrice :: IB_IN -> RIO App ()
 | 
			
		||||
handleTickPrice IB_TickPrice{..} = do
 | 
			
		||||
  charts <- appCharts . appRefs <$> ask
 | 
			
		||||
  tid2symbol <- tickerIdToSymbol . appRefs <$> ask
 | 
			
		||||
  msymbol <- (HM.!? tickerId) <$> liftIO (readTVarIO tid2symbol)
 | 
			
		||||
  case msymbol of
 | 
			
		||||
    Nothing -> return () --ignore
 | 
			
		||||
    Just s  -> do
 | 
			
		||||
        chartVar <- (HM.!s) <$> liftIO (readTVarIO charts)
 | 
			
		||||
        case tickType of
 | 
			
		||||
          IBTickType_Last_Price -> do
 | 
			
		||||
              t <- utctDayTime <$> liftIO getCurrentTime
 | 
			
		||||
              let cp = ChartPoint (TimePoint $ fromInteger. (`div` 1000000000000) . diffTimeToPicoseconds $ t) price []
 | 
			
		||||
              liftIO $ atomically $ modifyTVar chartVar (\c@Chart{..} -> c { chartData = chartData |> cp, chartDirty = True})
 | 
			
		||||
          _ -> return ()
 | 
			
		||||
handleTickPrice _ = error "impossible"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								src/Chart.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/Chart.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
{-# LANGUAGE OverloadedStrings #-}
 | 
			
		||||
{-# LANGUAGE TypeApplications #-}
 | 
			
		||||
{-# LANGUAGE DerivingVia #-}
 | 
			
		||||
module Chart (newChart, FillerException(..)) where
 | 
			
		||||
 | 
			
		||||
import Import
 | 
			
		||||
import Data.Time
 | 
			
		||||
import RIO.List
 | 
			
		||||
import RIO.List.Partial
 | 
			
		||||
import Data.FingerTree (FingerTree)
 | 
			
		||||
import Control.Concurrent (forkIO)
 | 
			
		||||
import qualified RIO.ByteString as BS
 | 
			
		||||
-- import Control.Exception
 | 
			
		||||
import qualified Data.HashMap.Strict as HM
 | 
			
		||||
import qualified Data.FingerTree as FT
 | 
			
		||||
 | 
			
		||||
import qualified Debug.Trace as D
 | 
			
		||||
 | 
			
		||||
data FillerException = QuitFiller
 | 
			
		||||
  deriving Show
 | 
			
		||||
 | 
			
		||||
instance Exception FillerException
 | 
			
		||||
 | 
			
		||||
deriving via Integer instance Hashable Day
 | 
			
		||||
 | 
			
		||||
newChart :: IBContract -> RIO App ()
 | 
			
		||||
newChart contract = do
 | 
			
		||||
  app <- ask
 | 
			
		||||
  let sym = (symbol :: IBContract -> Text) contract
 | 
			
		||||
      hmVar = appCharts . appRefs $ app
 | 
			
		||||
  hm <- liftIO . readTVarIO $ hmVar
 | 
			
		||||
  unless (sym `HM.member` hm) $ do
 | 
			
		||||
    c <- liftIO $ newTVarIO $ Chart FT.empty mempty undefined defChartSettings [] Nothing False
 | 
			
		||||
    tid <- liftIO $ forkIO $ fillChart app contract c
 | 
			
		||||
    liftIO $ atomically $ do
 | 
			
		||||
      modifyTVar' c (\Chart{..} -> let fillerThread = tid in Chart{..})
 | 
			
		||||
      modifyTVar' hmVar (HM.insert sym c)
 | 
			
		||||
 | 
			
		||||
fillChart :: App -> IBContract -> TVar Chart -> IO ()
 | 
			
		||||
fillChart app contract cVar = runRIO app $ do
 | 
			
		||||
  let sym = (symbol :: IBContract -> Text) contract
 | 
			
		||||
  (tickerMapVar :: TVar (HashMap Int Text)) <- tickerIdToSymbol. appRefs <$> ask
 | 
			
		||||
  alreadyAdded <- (sym `elem`) . HM.elems <$> liftIO (readTVarIO tickerMapVar)
 | 
			
		||||
  unless alreadyAdded $ do
 | 
			
		||||
    tickerId <- (+1) . foldl' max 1000 . HM.keys <$> liftIO (readTVarIO tickerMapVar)
 | 
			
		||||
    let cancelSubscription = liftIO $ atomically $ do
 | 
			
		||||
         modifyTVar tickerMapVar (HM.delete tickerId)
 | 
			
		||||
         -- TODO: send cancel-request
 | 
			
		||||
    let sendQ = twsConnectionSend $ twsConnectionRefs $ appRefs $ app
 | 
			
		||||
    liftIO $ atomically $ do
 | 
			
		||||
      modifyTVar tickerMapVar (HM.insert tickerId sym)
 | 
			
		||||
      writeTQueue sendQ $ Msg_IB_OUT $ IB_RequestMktData tickerId contract "233" False False
 | 
			
		||||
    handle (\QuitFiller -> cancelSubscription >> exitSuccess) $
 | 
			
		||||
      forever $ do
 | 
			
		||||
        -- chart dirty? set clean & begin work
 | 
			
		||||
        Chart{..} <- liftIO (readTVarIO cVar)
 | 
			
		||||
        when chartDirty $ do
 | 
			
		||||
          liftIO $ atomically $ modifyTVar cVar (\c -> c { chartDirty = False })
 | 
			
		||||
          let (TimePoint cacheUpdateStart) = fromMaybe (TimePoint 0) Nothing -- TODO: lastCacheUpdate
 | 
			
		||||
              cacheUpdateEnd   = 86400
 | 
			
		||||
              chunkChart :: Int -> Int -> Int -> FingerTree TimePoint ChartPoint -> [(TimePoint,[ChartPoint])]
 | 
			
		||||
              chunkChart from until range tree = go from range interval
 | 
			
		||||
                  where
 | 
			
		||||
                    lastItem = case FT.viewr interval of
 | 
			
		||||
                                FT.EmptyR -> until
 | 
			
		||||
                                (_ FT.:> ChartPoint{..}) -> (\(TimePoint x) -> x) timeOfDay
 | 
			
		||||
                    interval = FT.takeUntil (\(TimePoint x) -> x > until)
 | 
			
		||||
                             . FT.dropUntil (\(TimePoint x) -> x > from)
 | 
			
		||||
                             $ tree
 | 
			
		||||
                    go f i t
 | 
			
		||||
                      | f+i >= lastItem = [(TimePoint (f+i), toList t)]
 | 
			
		||||
                      | otherwise       = let (a, b) = FT.split (\(TimePoint x) -> x > f+i) t
 | 
			
		||||
                                          in (TimePoint (f+i),toList a) : go (f+i) i b
 | 
			
		||||
              chunkedChart = chunkChart cacheUpdateStart cacheUpdateEnd (chartResolution chartSettings) chartData
 | 
			
		||||
              cachePoints = takeWhile (\ChartPoint{..} -> (\(TimePoint x) -> x < cacheUpdateStart) timeOfDay) chartCache <> map toCachePoint chunkedChart
 | 
			
		||||
              toCachePoint :: (TimePoint,[ChartPoint]) -> ChartPoint
 | 
			
		||||
              toCachePoint (t,[]) = ChartPoint t (-1) []
 | 
			
		||||
              toCachePoint (t,as) = ChartPoint t c [OLHC o l h c]
 | 
			
		||||
                where
 | 
			
		||||
                  as' = pointValue <$> as
 | 
			
		||||
                  o = head as'
 | 
			
		||||
                  c = last as'
 | 
			
		||||
                  l = minimum as'
 | 
			
		||||
                  h = maximum as'
 | 
			
		||||
          let lUpdate = fmap fst . lastMaybe $ chunkedChart
 | 
			
		||||
          liftIO $ atomically $ modifyTVar cVar (\c -> c { chartCache = cachePoints, lastCacheUpdate = lUpdate })
 | 
			
		||||
          return ()
 | 
			
		||||
        threadDelay 1000000 -- sleep 5 seconds
 | 
			
		||||
							
								
								
									
										95
									
								
								src/IBClient/Connection.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/IBClient/Connection.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
			
		||||
{-# LANGUAGE TypeApplications #-}
 | 
			
		||||
module IBClient.Connection where
 | 
			
		||||
 | 
			
		||||
import Import
 | 
			
		||||
 | 
			
		||||
import Data.Binary
 | 
			
		||||
import Network.Socket
 | 
			
		||||
import Network.Socket.ByteString
 | 
			
		||||
 | 
			
		||||
import qualified Control.Exception as E
 | 
			
		||||
import qualified Data.ByteString as BS
 | 
			
		||||
import qualified Data.ByteString.Lazy as LBS
 | 
			
		||||
import qualified Data.ByteString.Char8 as BS8
 | 
			
		||||
import qualified Data.Text as T
 | 
			
		||||
 | 
			
		||||
forkClient :: App -> IO ()
 | 
			
		||||
forkClient app = runRIO app $ withRunInIO $ \run ->  withSocketsDo $ do
 | 
			
		||||
  let refs      = twsConnectionRefs $ appRefs app
 | 
			
		||||
      toSend    = twsConnectionSend refs
 | 
			
		||||
      toRecieve = twsConnectionRecieve refs
 | 
			
		||||
      cStatus   = twsConnectionStatus refs
 | 
			
		||||
      debugSend x = run $ logDebug (display $ T.pack $ "SENT    : " <> show x)
 | 
			
		||||
      debugRecv x = run $ logDebug (display $ T.pack $ "RECIEVED: " <> show x)
 | 
			
		||||
  connHost <- readTVarIO $ twsConnectionRefsHost refs
 | 
			
		||||
  connPort <- readTVarIO $ twsConnectionRefsPort refs
 | 
			
		||||
  atomically $ modifyTVar' cStatus (const TWSConnecting)
 | 
			
		||||
  -- TODO: throws IO-Exeption instead of returning empty list -> handle!
 | 
			
		||||
  addr:_ <- getAddrInfo (Just $ defaultHints { addrFlags = [AI_NUMERICHOST, AI_NUMERICSERV], addrProtocol = 0, addrSocketType = Stream}) (Just connHost) (Just connPort)
 | 
			
		||||
  run $ logDebug $ displayShow  addr
 | 
			
		||||
  E.bracketOnError (socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)) close $ \sock -> do
 | 
			
		||||
    connect sock $ addrAddress addr
 | 
			
		||||
    let connStr = BS8.pack "API\0\0\0\0\tv100..157"
 | 
			
		||||
    sendAll sock connStr
 | 
			
		||||
    debugSend connStr
 | 
			
		||||
    answer <- recvAll sock
 | 
			
		||||
    run $ logDebug (displayShow (decode . LBS.fromStrict <$> answer :: Maybe IBGenericMessage))
 | 
			
		||||
    -- if we have the answer we are connected
 | 
			
		||||
    let idStr = LBS.toStrict $ encode $ IB_StartAPI "2" "69" -- version 2, client-id: 69
 | 
			
		||||
    sendAll sock idStr
 | 
			
		||||
    debugSend idStr
 | 
			
		||||
    atomically $ modifyTVar' cStatus (const TWSConnected)
 | 
			
		||||
    run $ logInfo $ display ("Connected to TWS" :: Text)
 | 
			
		||||
    let go True = do
 | 
			
		||||
          -- abort connection, close everything
 | 
			
		||||
          return ()
 | 
			
		||||
        go False = do
 | 
			
		||||
          -- race: wait for MSG in Queue or for answer on socket
 | 
			
		||||
          input <- race (atomically $ readTQueue toSend)
 | 
			
		||||
                        (recvAll sock)
 | 
			
		||||
          case input of
 | 
			
		||||
            -- we want to disconnect
 | 
			
		||||
            Left IBDisconnect -> go True
 | 
			
		||||
            Left (Msg_IB_OUT x) -> do
 | 
			
		||||
              let msg = LBS.toStrict $ encode x
 | 
			
		||||
              debugSend msg
 | 
			
		||||
              sendAll sock msg
 | 
			
		||||
            -- we lost connection
 | 
			
		||||
            Right Nothing     -> do
 | 
			
		||||
                                    atomically $ do
 | 
			
		||||
                                      writeTQueue toRecieve IBServerGone
 | 
			
		||||
                                      modifyTVar' cStatus (const TWSDisconnected)
 | 
			
		||||
                                    run $ logWarn $ display $ T.pack "Lost connection to TWS, reconnecting..."
 | 
			
		||||
                                    forkClient app
 | 
			
		||||
            Right (Just x)    -> do debugRecv x
 | 
			
		||||
                                    parseMessage x
 | 
			
		||||
                                      where
 | 
			
		||||
                                        parseMessage "" = return ()
 | 
			
		||||
                                        parseMessage m = do
 | 
			
		||||
                                          let d = decodeOrFail @IB_IN (LBS.fromStrict m)
 | 
			
		||||
                                          case d of
 | 
			
		||||
                                            Right (rest, offset, result) -> do
 | 
			
		||||
                                              atomically $ writeTQueue toRecieve (Msg_IB_IN result)
 | 
			
		||||
                                              parseMessage (LBS.toStrict rest)
 | 
			
		||||
                                            Left (rest, offset, err) -> do
 | 
			
		||||
                                              run $ logInfo (display $ T.pack $ "Could not understand message: "<> ppShow err <> " ... skipping.\nRAW: " <> show m)
 | 
			
		||||
                                              if m == "\NUL" then do
 | 
			
		||||
                                                run $ logInfo (display $ T.pack "killing NUL")
 | 
			
		||||
                                                parseMessage (LBS.toStrict $ LBS.tail rest)
 | 
			
		||||
                                              else
 | 
			
		||||
                                                parseMessage (LBS.toStrict rest)
 | 
			
		||||
          go False
 | 
			
		||||
    go False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
recvAll :: Socket -> IO (Maybe ByteString)
 | 
			
		||||
recvAll s = do
 | 
			
		||||
    d <- recv s 4096
 | 
			
		||||
    let l = BS.length d
 | 
			
		||||
    if
 | 
			
		||||
       | l == 0    -> return Nothing
 | 
			
		||||
       | l < 4096  -> return $ Just d
 | 
			
		||||
       | l == 4096 -> do
 | 
			
		||||
                        next <- recvAll s
 | 
			
		||||
                        return $ (d<>) <$> next
 | 
			
		||||
       | otherwise -> error "recvAll: recv got more bytes then requested. Impossible according to RFC"
 | 
			
		||||
							
								
								
									
										581
									
								
								src/IBClient/Types.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										581
									
								
								src/IBClient/Types.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,581 @@
 | 
			
		||||
{-# HLINT ignore "Use camelCase" #-}
 | 
			
		||||
{-# LANGUAGE DerivingStrategies #-}
 | 
			
		||||
{-# LANGUAGE DeriveAnyClass #-}
 | 
			
		||||
{-# OPTIONS_GHC -Wno-partial-fields #-}
 | 
			
		||||
module IBClient.Types where
 | 
			
		||||
 | 
			
		||||
import Data.Binary
 | 
			
		||||
import Data.Binary.Get
 | 
			
		||||
import Data.Binary.Put
 | 
			
		||||
import Data.Text.Encoding
 | 
			
		||||
import Data.Maybe
 | 
			
		||||
import Data.Default
 | 
			
		||||
import Data.Aeson (FromJSON, ToJSON)
 | 
			
		||||
import GHC.Enum (Enum(..))
 | 
			
		||||
import RIO
 | 
			
		||||
import RIO.List
 | 
			
		||||
import RIO.List.Partial
 | 
			
		||||
import qualified Data.ByteString as BS
 | 
			
		||||
import qualified Data.ByteString.Char8 as BS8
 | 
			
		||||
import qualified Data.ByteString.Lazy as LBS
 | 
			
		||||
import qualified Data.ByteString.Lazy.Char8 as LBS8
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import qualified Debug.Trace as D
 | 
			
		||||
 | 
			
		||||
data Msg_IB_OUT = IBDisconnect
 | 
			
		||||
                | Msg_IB_OUT IB_OUT
 | 
			
		||||
               deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
data Msg_IB_IN = IBServerGone
 | 
			
		||||
               | Msg_IB_IN IB_IN
 | 
			
		||||
               deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
data IBTypes = IBString ByteString
 | 
			
		||||
             | IBBool Bool
 | 
			
		||||
             | IBArray [ByteString]
 | 
			
		||||
            deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
toBS :: IBTypes -> ByteString
 | 
			
		||||
toBS (IBString t)   = t
 | 
			
		||||
toBS (IBArray a)    = (BS8.pack . show . length $ a) <> BS.intercalate "\0" a <> "\0"
 | 
			
		||||
toBS (IBBool True)  = "1"--BS.pack [0,0,0,1] -- bool == 32-bit int in IB
 | 
			
		||||
toBS (IBBool False) = "0"--BS.pack [0,0,0,0] -- bool == 32-bit int in IB
 | 
			
		||||
 | 
			
		||||
newtype IBGenericMessage = IBGenericMessage
 | 
			
		||||
                   { fields :: [IBTypes]
 | 
			
		||||
                   } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
instance Binary IBGenericMessage where
 | 
			
		||||
  put (IBGenericMessage f) = do 
 | 
			
		||||
    let msg = BS.intercalate "\0" (toBS <$> f) <> "\0"
 | 
			
		||||
    putWord32be . fromIntegral . BS.length $ msg
 | 
			
		||||
    putByteString msg
 | 
			
		||||
  get = do
 | 
			
		||||
    len <- getWord32be
 | 
			
		||||
    D.traceShow len $ return ()
 | 
			
		||||
    fields <- BS.split 0 . BS.init <$> getByteString (fromIntegral len)
 | 
			
		||||
    D.traceShow fields $ return ()
 | 
			
		||||
    return $ IBGenericMessage $ IBString <$> fields
 | 
			
		||||
 | 
			
		||||
data IB_OUT = IB_StartAPI { version :: Text, clientId :: Text }
 | 
			
		||||
            | IB_RequestPositions
 | 
			
		||||
            | IB_RequestAccountData { subscribe :: Bool, acctCode :: Text }
 | 
			
		||||
            | IB_RequestMatchingSymbol { reqId :: Int, symbol :: Text }
 | 
			
		||||
            | IB_RequestMarketDataType { dataType :: IBMarketDataType }
 | 
			
		||||
            | IB_RequestMktData { tickerId :: Int, contract :: IBContract, genericTickList :: Text, snapshot :: Bool, regulatorySnapshot :: Bool }
 | 
			
		||||
            deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
tToIB :: Text -> IBTypes
 | 
			
		||||
tToIB = IBString . encodeUtf8
 | 
			
		||||
 | 
			
		||||
iToIB :: Int -> IBTypes
 | 
			
		||||
iToIB = IBString . BS8.pack . show
 | 
			
		||||
 | 
			
		||||
fToIB :: Float -> IBTypes
 | 
			
		||||
fToIB = IBString . BS8.pack . show
 | 
			
		||||
 | 
			
		||||
clToIB :: [IBComboLeg] -> [IBTypes]
 | 
			
		||||
clToIB as = iToIB (length as) : concatMap (\IBComboLeg{..} -> [iToIB conId, iToIB ratio, tToIB action, tToIB exchange]) as
 | 
			
		||||
 | 
			
		||||
dncToIB :: Maybe IBDeltaNeutralContract -> [IBTypes]
 | 
			
		||||
dncToIB Nothing                           = []
 | 
			
		||||
dncToIB (Just IBDeltaNeutralContract{..}) = [iToIB 1, iToIB conId, fToIB delta, fToIB price]
 | 
			
		||||
 | 
			
		||||
instance Binary IB_OUT where
 | 
			
		||||
  put (IB_StartAPI v c)           = put (IBGenericMessage [IBString "71", tToIB v, tToIB c, IBString ""])
 | 
			
		||||
  put  IB_RequestPositions        = put (IBGenericMessage [IBString "61", IBString "v"])
 | 
			
		||||
  put (IB_RequestAccountData s a) = put (IBGenericMessage [IBString "6", IBString "2", IBBool s, tToIB a])
 | 
			
		||||
  put (IB_RequestMatchingSymbol i s) = put (IBGenericMessage [IBString "81", iToIB i, tToIB s])
 | 
			
		||||
  put (IB_RequestMktData t IBContract{..} l s r) = put $ D.traceShowId (IBGenericMessage $ [IBString "1", IBString "11", iToIB t, iToIB conId, tToIB symbol, tToIB secType, tToIB lastTradeDate, fToIB strike, tToIB right, tToIB multiplier, tToIB exchange, tToIB primaryExchange, tToIB currency, tToIB localSymbol, tToIB tradingClass] <> clToIB comboLegs <> dncToIB deltaNeutralContract <> [tToIB l, iToIB (if s then 1 else 0), iToIB (if r then 1 else 0), tToIB ""])
 | 
			
		||||
  put (IB_RequestMarketDataType t) = put (IBGenericMessage [IBString "59", IBString "1", iToIB $ fromEnum t])
 | 
			
		||||
  
 | 
			
		||||
  get = do f <- fmap (\(IBString x) -> x) . fields <$> get
 | 
			
		||||
           case headMaybe f of
 | 
			
		||||
             Just "71" -> return $ IB_StartAPI (decodeUtf8 $ f!!1) (decodeUtf8 $ f!!2)
 | 
			
		||||
             Just "6"  -> return $ IB_RequestAccountData {- ignore version -} (all (==0) . BS.unpack $ f!!2) (decodeUtf8 $ f!!3)
 | 
			
		||||
             Just "59" -> return $ IB_RequestMarketDataType {- ignore version -} (toEnum . fromJust . readMaybe . BS8.unpack $ f!!2)
 | 
			
		||||
             Just x    -> fail $ "unkonwn IB_OUT type" <> BS8.unpack x
 | 
			
		||||
             Nothing   -> fail $ "No Fields"
 | 
			
		||||
 | 
			
		||||
data IBContract = IBContract
 | 
			
		||||
                { conId                :: Int
 | 
			
		||||
                , symbol               :: Text
 | 
			
		||||
                , secType              :: Text
 | 
			
		||||
                , lastTradeDate        :: Text
 | 
			
		||||
                , strike               :: Float
 | 
			
		||||
                , right                :: Text
 | 
			
		||||
                , multiplier           :: Text
 | 
			
		||||
                , exchange             :: Text -- ^ can be SMART
 | 
			
		||||
                , primaryExchange      :: Text -- ^ actual exchange - MUST NOT BE SMART
 | 
			
		||||
                , currency             :: Text
 | 
			
		||||
                , localSymbol          :: Text
 | 
			
		||||
                , tradingClass         :: Text
 | 
			
		||||
                , includeExpired       :: Bool
 | 
			
		||||
                , secIdType            :: Text
 | 
			
		||||
                , secId                :: Text
 | 
			
		||||
                , comboLegsDescrip     :: Text -- ^ received in open order 14 and up for all combos
 | 
			
		||||
                , comboLegs            :: [IBComboLeg]
 | 
			
		||||
                , deltaNeutralContract :: Maybe IBDeltaNeutralContract
 | 
			
		||||
                } deriving (Show, Eq, Generic)
 | 
			
		||||
                  deriving anyclass (FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
instance Default IBContract where
 | 
			
		||||
  def = IBContract 0 "" "" "" 0 "" "" "" "" "" "" "" False "" "" "" [] Nothing
 | 
			
		||||
 | 
			
		||||
data IBComboLeg = IBComboLeg
 | 
			
		||||
                { conId              :: Int
 | 
			
		||||
                , ratio              :: Int
 | 
			
		||||
                , action             :: Text -- ^ BUY/SELL/SSHORT
 | 
			
		||||
                , exchange           :: Text
 | 
			
		||||
                , openClose          :: Int -- ^ LegOpenClose enum values
 | 
			
		||||
                , shortSaleSlot      :: Int
 | 
			
		||||
                , designatedLocation :: Text
 | 
			
		||||
                , exemptCode         :: Int
 | 
			
		||||
                } deriving (Show, Eq, Generic)
 | 
			
		||||
                  deriving anyclass (FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
instance Default IBComboLeg where
 | 
			
		||||
  def = IBComboLeg 0 0 "" "" 0 0 "" (negate 1)
 | 
			
		||||
 | 
			
		||||
data IBDeltaNeutralContract = IBDeltaNeutralContract
 | 
			
		||||
                            { conId :: Int
 | 
			
		||||
                            , delta :: Float
 | 
			
		||||
                            , price :: Float
 | 
			
		||||
                            } deriving (Show, Eq, Generic)
 | 
			
		||||
                              deriving anyclass (FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
instance Default IBDeltaNeutralContract where
 | 
			
		||||
  def = IBDeltaNeutralContract 0 0 0
 | 
			
		||||
 | 
			
		||||
data IBMarketDataType = RealTime
 | 
			
		||||
                      | Frozen
 | 
			
		||||
                      | Delayed
 | 
			
		||||
                      | DelayedFrozen
 | 
			
		||||
                      deriving (Show, Eq, Generic)
 | 
			
		||||
                      deriving anyclass (FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
instance Default IBMarketDataType where
 | 
			
		||||
  def = DelayedFrozen
 | 
			
		||||
 | 
			
		||||
instance Enum IBMarketDataType where
 | 
			
		||||
  toEnum 1               = RealTime
 | 
			
		||||
  toEnum 2               = Frozen
 | 
			
		||||
  toEnum 3               = Delayed
 | 
			
		||||
  toEnum 4               = DelayedFrozen
 | 
			
		||||
  toEnum _               = def
 | 
			
		||||
  fromEnum RealTime      = 1
 | 
			
		||||
  fromEnum Frozen        = 2
 | 
			
		||||
  fromEnum Delayed       = 3
 | 
			
		||||
  fromEnum DelayedFrozen = 4
 | 
			
		||||
 | 
			
		||||
data IBTickType = Unknown Int
 | 
			
		||||
                | IBTickType_Bid_Size
 | 
			
		||||
                | IBTickType_Bid_Price
 | 
			
		||||
                | IBTickType_Ask_Price
 | 
			
		||||
                | IBTickType_Ask_Size
 | 
			
		||||
                | IBTickType_Last_Price
 | 
			
		||||
                | IBTickType_Last_Size
 | 
			
		||||
                | IBTickType_High
 | 
			
		||||
                | IBTickType_Low
 | 
			
		||||
                | IBTickType_Volume
 | 
			
		||||
                | IBTickType_Close_Price
 | 
			
		||||
                | IBTickType_Bid_Option_Computation
 | 
			
		||||
                | IBTickType_Ask_Option_Computation
 | 
			
		||||
                | IBTickType_Last_Option_Computation
 | 
			
		||||
                | IBTickType_Model_Option_Computation
 | 
			
		||||
                | IBTickType_Open_Tick
 | 
			
		||||
                | IBTickType_Low_13_Weeks
 | 
			
		||||
                | IBTickType_High_13_Weeks
 | 
			
		||||
                | IBTickType_Low_26_Weeks
 | 
			
		||||
                | IBTickType_High_26_Weeks
 | 
			
		||||
                | IBTickType_Low_52_Weeks
 | 
			
		||||
                | IBTickType_High_52_Weeks
 | 
			
		||||
                | IBTickType_Average_Volume
 | 
			
		||||
                | IBTickType_Open_Interest
 | 
			
		||||
                | IBTickType_Option_Historical_Volatility
 | 
			
		||||
                | IBTickType_Option_Implied_Volatility
 | 
			
		||||
                | IBTickType_Option_Bid_Exchange
 | 
			
		||||
                | IBTickType_Option_Ask_Exchange
 | 
			
		||||
                | IBTickType_Option_Call_Open_Interest
 | 
			
		||||
                | IBTickType_Option_Put_Open_Interest
 | 
			
		||||
                | IBTickType_Option_Call_Volume
 | 
			
		||||
                | IBTickType_Option_Put_Volume
 | 
			
		||||
                | IBTickType_Index_Future_Premium
 | 
			
		||||
                | IBTickType_Bid_Exchange
 | 
			
		||||
                | IBTickType_Ask_Exchange
 | 
			
		||||
                | IBTickType_Auction_Volume
 | 
			
		||||
                | IBTickType_Auction_Price
 | 
			
		||||
                | IBTickType_Auction_Imbalance
 | 
			
		||||
                | IBTickType_Mark_Price
 | 
			
		||||
                | IBTickType_Bid_EFP_Computation
 | 
			
		||||
                | IBTickType_Ask_EFP_Computation
 | 
			
		||||
                | IBTickType_Last_EFP_Computation
 | 
			
		||||
                | IBTickType_Open_EFP_Computation
 | 
			
		||||
                | IBTickType_High_EFP_Computation
 | 
			
		||||
                | IBTickType_Low_EFP_Computation
 | 
			
		||||
                | IBTickType_Close_EFP_Computation
 | 
			
		||||
                | IBTickType_Last_Timestamp
 | 
			
		||||
                | IBTickType_Shortable
 | 
			
		||||
                | IBTickType_RT_Volume
 | 
			
		||||
                | IBTickType_Halted
 | 
			
		||||
                | IBTickType_Bid_Yield
 | 
			
		||||
                | IBTickType_Ask_Yield
 | 
			
		||||
                | IBTickType_Last_Yield
 | 
			
		||||
                | IBTickType_Custom_Option_Computation
 | 
			
		||||
                | IBTickType_Trade_Count
 | 
			
		||||
                | IBTickType_Trade_Rate
 | 
			
		||||
                | IBTickType_Volume_Rate
 | 
			
		||||
                | IBTickType_Last_RTH_Trade
 | 
			
		||||
                | IBTickType_RT_Historical_Volatility
 | 
			
		||||
                | IBTickType_IB_Dividends
 | 
			
		||||
                | IBTickType_Bond_Factor_Multiplier
 | 
			
		||||
                | IBTickType_Regulatory_Imbalance
 | 
			
		||||
                | IBTickType_News
 | 
			
		||||
                | IBTickType_ShortTerm_Volume_3_Minutes
 | 
			
		||||
                | IBTickType_ShortTerm_Volume_5_Minutes
 | 
			
		||||
                | IBTickType_ShortTerm_Volume_10_Minutes
 | 
			
		||||
                | IBTickType_Delayed_Bid
 | 
			
		||||
                | IBTickType_Delayed_Ask
 | 
			
		||||
                | IBTickType_Delayed_Last
 | 
			
		||||
                | IBTickType_Delayed_Bid_Size
 | 
			
		||||
                | IBTickType_Delayed_Ask_Size
 | 
			
		||||
                | IBTickType_Delayed_Last_Size
 | 
			
		||||
                | IBTickType_Delayed_High_Price
 | 
			
		||||
                | IBTickType_Delayed_Low_Price
 | 
			
		||||
                | IBTickType_Delayed_Volume
 | 
			
		||||
                | IBTickType_Delayed_Close
 | 
			
		||||
                | IBTickType_Delayed_Open
 | 
			
		||||
                | IBTickType_RT_Trade_Volume
 | 
			
		||||
                | IBTickType_Creditman_mark_price
 | 
			
		||||
                | IBTickType_Creditman_slow_mark_price
 | 
			
		||||
                | IBTickType_Delayed_Bid_Option
 | 
			
		||||
                | IBTickType_Delayed_Ask_Option
 | 
			
		||||
                | IBTickType_Delayed_Last_Option
 | 
			
		||||
                | IBTickType_Delayed_Model_Option
 | 
			
		||||
                | IBTickType_Last_Exchange
 | 
			
		||||
                | IBTickType_Last_Regulatory_Time
 | 
			
		||||
                | IBTickType_Futures_Open_Interest
 | 
			
		||||
                | IBTickType_Average_Option_Volume
 | 
			
		||||
                | IBTickType_Delayed_Last_Timestamp
 | 
			
		||||
                | IBTickType_Shortable_Shares
 | 
			
		||||
                | IBTickType_ETF_Nav_Close
 | 
			
		||||
                | IBTickType_ETF_Nav_Prior_Close
 | 
			
		||||
                | IBTickType_ETF_Nav_Bid
 | 
			
		||||
                | IBTickType_ETF_Nav_Ask
 | 
			
		||||
                | IBTickType_ETF_Nav_Last
 | 
			
		||||
                | IBTickType_ETF_Nav_Frozen_Last
 | 
			
		||||
                | IBTickType_ETF_Nav_High
 | 
			
		||||
                | IBTickType_ETF_Nav_Low
 | 
			
		||||
  deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
instance Enum IBTickType where
 | 
			
		||||
  toEnum  0                    = IBTickType_Bid_Size
 | 
			
		||||
  toEnum  1                    = IBTickType_Bid_Price
 | 
			
		||||
  toEnum  2                    = IBTickType_Ask_Price
 | 
			
		||||
  toEnum  3                    = IBTickType_Ask_Size
 | 
			
		||||
  toEnum  4                    = IBTickType_Last_Price
 | 
			
		||||
  toEnum  5                    = IBTickType_Last_Size
 | 
			
		||||
  toEnum  6                    = IBTickType_High
 | 
			
		||||
  toEnum  7                    = IBTickType_Low
 | 
			
		||||
  toEnum  8                    = IBTickType_Volume
 | 
			
		||||
  toEnum  9                    = IBTickType_Close_Price
 | 
			
		||||
  toEnum 10                    = IBTickType_Bid_Option_Computation
 | 
			
		||||
  toEnum 11                    = IBTickType_Ask_Option_Computation
 | 
			
		||||
  toEnum 12                    = IBTickType_Last_Option_Computation
 | 
			
		||||
  toEnum 13                    = IBTickType_Model_Option_Computation
 | 
			
		||||
  toEnum 14                    = IBTickType_Open_Tick
 | 
			
		||||
  toEnum 15                    = IBTickType_Low_13_Weeks
 | 
			
		||||
  toEnum 16                    = IBTickType_High_13_Weeks
 | 
			
		||||
  toEnum 17                    = IBTickType_Low_26_Weeks
 | 
			
		||||
  toEnum 18                    = IBTickType_High_26_Weeks
 | 
			
		||||
  toEnum 19                    = IBTickType_Low_52_Weeks
 | 
			
		||||
  toEnum 20                    = IBTickType_High_52_Weeks
 | 
			
		||||
  toEnum 21                    = IBTickType_Average_Volume
 | 
			
		||||
  toEnum 22                    = IBTickType_Open_Interest
 | 
			
		||||
  toEnum 23                    = IBTickType_Option_Historical_Volatility
 | 
			
		||||
  toEnum 24                    = IBTickType_Option_Implied_Volatility
 | 
			
		||||
  toEnum 25                    = IBTickType_Option_Bid_Exchange
 | 
			
		||||
  toEnum 26                    = IBTickType_Option_Ask_Exchange
 | 
			
		||||
  toEnum 27                    = IBTickType_Option_Call_Open_Interest
 | 
			
		||||
  toEnum 28                    = IBTickType_Option_Put_Open_Interest
 | 
			
		||||
  toEnum 29                    = IBTickType_Option_Call_Volume
 | 
			
		||||
  toEnum 30                    = IBTickType_Option_Put_Volume
 | 
			
		||||
  toEnum 31                    = IBTickType_Index_Future_Premium
 | 
			
		||||
  toEnum 32                    = IBTickType_Bid_Exchange
 | 
			
		||||
  toEnum 33                    = IBTickType_Ask_Exchange
 | 
			
		||||
  toEnum 34                    = IBTickType_Auction_Volume
 | 
			
		||||
  toEnum 35                    = IBTickType_Auction_Price
 | 
			
		||||
  toEnum 36                    = IBTickType_Auction_Imbalance
 | 
			
		||||
  toEnum 37                    = IBTickType_Mark_Price
 | 
			
		||||
  toEnum 38                    = IBTickType_Bid_EFP_Computation
 | 
			
		||||
  toEnum 39                    = IBTickType_Ask_EFP_Computation
 | 
			
		||||
  toEnum 40                    = IBTickType_Last_EFP_Computation
 | 
			
		||||
  toEnum 41                    = IBTickType_Open_EFP_Computation
 | 
			
		||||
  toEnum 42                    = IBTickType_High_EFP_Computation
 | 
			
		||||
  toEnum 43                    = IBTickType_Low_EFP_Computation
 | 
			
		||||
  toEnum 44                    = IBTickType_Close_EFP_Computation
 | 
			
		||||
  toEnum 45                    = IBTickType_Last_Timestamp
 | 
			
		||||
  toEnum 46                    = IBTickType_Shortable
 | 
			
		||||
  toEnum 48                    = IBTickType_RT_Volume
 | 
			
		||||
  toEnum 49                    = IBTickType_Halted
 | 
			
		||||
  toEnum 50                    = IBTickType_Bid_Yield
 | 
			
		||||
  toEnum 51                    = IBTickType_Ask_Yield
 | 
			
		||||
  toEnum 52                    = IBTickType_Last_Yield
 | 
			
		||||
  toEnum 53                    = IBTickType_Custom_Option_Computation
 | 
			
		||||
  toEnum 54                    = IBTickType_Trade_Count
 | 
			
		||||
  toEnum 55                    = IBTickType_Trade_Rate
 | 
			
		||||
  toEnum 56                    = IBTickType_Volume_Rate
 | 
			
		||||
  toEnum 57                    = IBTickType_Last_RTH_Trade
 | 
			
		||||
  toEnum 58                    = IBTickType_RT_Historical_Volatility
 | 
			
		||||
  toEnum 59                    = IBTickType_IB_Dividends
 | 
			
		||||
  toEnum 60                    = IBTickType_Bond_Factor_Multiplier
 | 
			
		||||
  toEnum 61                    = IBTickType_Regulatory_Imbalance
 | 
			
		||||
  toEnum 62                    = IBTickType_News
 | 
			
		||||
  toEnum 63                    = IBTickType_ShortTerm_Volume_3_Minutes
 | 
			
		||||
  toEnum 64                    = IBTickType_ShortTerm_Volume_5_Minutes
 | 
			
		||||
  toEnum 65                    = IBTickType_ShortTerm_Volume_10_Minutes
 | 
			
		||||
  toEnum 66                    = IBTickType_Delayed_Bid
 | 
			
		||||
  toEnum 67                    = IBTickType_Delayed_Ask
 | 
			
		||||
  toEnum 68                    = IBTickType_Delayed_Last
 | 
			
		||||
  toEnum 69                    = IBTickType_Delayed_Bid_Size
 | 
			
		||||
  toEnum 70                    = IBTickType_Delayed_Ask_Size
 | 
			
		||||
  toEnum 71                    = IBTickType_Delayed_Last_Size
 | 
			
		||||
  toEnum 72                    = IBTickType_Delayed_High_Price
 | 
			
		||||
  toEnum 73                    = IBTickType_Delayed_Low_Price
 | 
			
		||||
  toEnum 74                    = IBTickType_Delayed_Volume
 | 
			
		||||
  toEnum 75                    = IBTickType_Delayed_Close
 | 
			
		||||
  toEnum 76                    = IBTickType_Delayed_Open
 | 
			
		||||
  toEnum 77                    = IBTickType_RT_Trade_Volume
 | 
			
		||||
  toEnum 78                    = IBTickType_Creditman_mark_price
 | 
			
		||||
  toEnum 79                    = IBTickType_Creditman_slow_mark_price
 | 
			
		||||
  toEnum 80                    = IBTickType_Delayed_Bid_Option
 | 
			
		||||
  toEnum 81                    = IBTickType_Delayed_Ask_Option
 | 
			
		||||
  toEnum 82                    = IBTickType_Delayed_Last_Option
 | 
			
		||||
  toEnum 83                    = IBTickType_Delayed_Model_Option
 | 
			
		||||
  toEnum 84                    = IBTickType_Last_Exchange
 | 
			
		||||
  toEnum 85                    = IBTickType_Last_Regulatory_Time
 | 
			
		||||
  toEnum 86                    = IBTickType_Futures_Open_Interest
 | 
			
		||||
  toEnum 87                    = IBTickType_Average_Option_Volume
 | 
			
		||||
  toEnum 88                    = IBTickType_Delayed_Last_Timestamp
 | 
			
		||||
  toEnum 89                    = IBTickType_Shortable_Shares
 | 
			
		||||
  toEnum 92                    = IBTickType_ETF_Nav_Close
 | 
			
		||||
  toEnum 93                    = IBTickType_ETF_Nav_Prior_Close
 | 
			
		||||
  toEnum 94                    = IBTickType_ETF_Nav_Bid
 | 
			
		||||
  toEnum 95                    = IBTickType_ETF_Nav_Ask
 | 
			
		||||
  toEnum 96                    = IBTickType_ETF_Nav_Last
 | 
			
		||||
  toEnum 97                    = IBTickType_ETF_Nav_Frozen_Last
 | 
			
		||||
  toEnum 98                    = IBTickType_ETF_Nav_High
 | 
			
		||||
  toEnum 99                    = IBTickType_ETF_Nav_Low
 | 
			
		||||
  toEnum x                     = D.trace ("Unknown tick-type-id: " <> show x) $ Unknown x
 | 
			
		||||
  fromEnum (Unknown x)         = x
 | 
			
		||||
  fromEnum IBTickType_Bid_Size                     =  0
 | 
			
		||||
  fromEnum IBTickType_Bid_Price                    =  1
 | 
			
		||||
  fromEnum IBTickType_Ask_Price                    =  2
 | 
			
		||||
  fromEnum IBTickType_Ask_Size                     =  3
 | 
			
		||||
  fromEnum IBTickType_Last_Price                   =  4
 | 
			
		||||
  fromEnum IBTickType_Last_Size                    =  5
 | 
			
		||||
  fromEnum IBTickType_High                         =  6
 | 
			
		||||
  fromEnum IBTickType_Low                          =  7
 | 
			
		||||
  fromEnum IBTickType_Volume                       =  8
 | 
			
		||||
  fromEnum IBTickType_Close_Price                  =  9
 | 
			
		||||
  fromEnum IBTickType_Bid_Option_Computation       = 10
 | 
			
		||||
  fromEnum IBTickType_Ask_Option_Computation       = 11
 | 
			
		||||
  fromEnum IBTickType_Last_Option_Computation      = 12
 | 
			
		||||
  fromEnum IBTickType_Model_Option_Computation     = 13
 | 
			
		||||
  fromEnum IBTickType_Open_Tick                    = 14
 | 
			
		||||
  fromEnum IBTickType_Low_13_Weeks                 = 15
 | 
			
		||||
  fromEnum IBTickType_High_13_Weeks                = 16
 | 
			
		||||
  fromEnum IBTickType_Low_26_Weeks                 = 17
 | 
			
		||||
  fromEnum IBTickType_High_26_Weeks                = 18
 | 
			
		||||
  fromEnum IBTickType_Low_52_Weeks                 = 19
 | 
			
		||||
  fromEnum IBTickType_High_52_Weeks                = 20
 | 
			
		||||
  fromEnum IBTickType_Average_Volume               = 21
 | 
			
		||||
  fromEnum IBTickType_Open_Interest                = 22
 | 
			
		||||
  fromEnum IBTickType_Option_Historical_Volatility = 23
 | 
			
		||||
  fromEnum IBTickType_Option_Implied_Volatility    = 24
 | 
			
		||||
  fromEnum IBTickType_Option_Bid_Exchange          = 25
 | 
			
		||||
  fromEnum IBTickType_Option_Ask_Exchange          = 26
 | 
			
		||||
  fromEnum IBTickType_Option_Call_Open_Interest    = 27
 | 
			
		||||
  fromEnum IBTickType_Option_Put_Open_Interest     = 28
 | 
			
		||||
  fromEnum IBTickType_Option_Call_Volume           = 29
 | 
			
		||||
  fromEnum IBTickType_Option_Put_Volume            = 30
 | 
			
		||||
  fromEnum IBTickType_Index_Future_Premium         = 31
 | 
			
		||||
  fromEnum IBTickType_Bid_Exchange                 = 32
 | 
			
		||||
  fromEnum IBTickType_Ask_Exchange                 = 33
 | 
			
		||||
  fromEnum IBTickType_Auction_Volume               = 34
 | 
			
		||||
  fromEnum IBTickType_Auction_Price                = 35
 | 
			
		||||
  fromEnum IBTickType_Auction_Imbalance            = 36
 | 
			
		||||
  fromEnum IBTickType_Mark_Price                   = 37
 | 
			
		||||
  fromEnum IBTickType_Bid_EFP_Computation          = 38
 | 
			
		||||
  fromEnum IBTickType_Ask_EFP_Computation          = 39
 | 
			
		||||
  fromEnum IBTickType_Last_EFP_Computation         = 40
 | 
			
		||||
  fromEnum IBTickType_Open_EFP_Computation         = 41
 | 
			
		||||
  fromEnum IBTickType_High_EFP_Computation         = 42
 | 
			
		||||
  fromEnum IBTickType_Low_EFP_Computation          = 43
 | 
			
		||||
  fromEnum IBTickType_Close_EFP_Computation        = 44
 | 
			
		||||
  fromEnum IBTickType_Last_Timestamp               = 45
 | 
			
		||||
  fromEnum IBTickType_Shortable                    = 46
 | 
			
		||||
  fromEnum IBTickType_RT_Volume                    = 48
 | 
			
		||||
  fromEnum IBTickType_Halted                       = 49
 | 
			
		||||
  fromEnum IBTickType_Bid_Yield                    = 50
 | 
			
		||||
  fromEnum IBTickType_Ask_Yield                    = 51
 | 
			
		||||
  fromEnum IBTickType_Last_Yield                   = 52
 | 
			
		||||
  fromEnum IBTickType_Custom_Option_Computation    = 53
 | 
			
		||||
  fromEnum IBTickType_Trade_Count                  = 54
 | 
			
		||||
  fromEnum IBTickType_Trade_Rate                   = 55
 | 
			
		||||
  fromEnum IBTickType_Volume_Rate                  = 56
 | 
			
		||||
  fromEnum IBTickType_Last_RTH_Trade               = 57
 | 
			
		||||
  fromEnum IBTickType_RT_Historical_Volatility     = 58
 | 
			
		||||
  fromEnum IBTickType_IB_Dividends                 = 59
 | 
			
		||||
  fromEnum IBTickType_Bond_Factor_Multiplier       = 60
 | 
			
		||||
  fromEnum IBTickType_Regulatory_Imbalance         = 61
 | 
			
		||||
  fromEnum IBTickType_News                         = 62
 | 
			
		||||
  fromEnum IBTickType_ShortTerm_Volume_3_Minutes   = 63
 | 
			
		||||
  fromEnum IBTickType_ShortTerm_Volume_5_Minutes   = 64
 | 
			
		||||
  fromEnum IBTickType_ShortTerm_Volume_10_Minutes  = 65
 | 
			
		||||
  fromEnum IBTickType_Delayed_Bid                  = 66
 | 
			
		||||
  fromEnum IBTickType_Delayed_Ask                  = 67
 | 
			
		||||
  fromEnum IBTickType_Delayed_Last                 = 68
 | 
			
		||||
  fromEnum IBTickType_Delayed_Bid_Size             = 69
 | 
			
		||||
  fromEnum IBTickType_Delayed_Ask_Size             = 70
 | 
			
		||||
  fromEnum IBTickType_Delayed_Last_Size            = 71
 | 
			
		||||
  fromEnum IBTickType_Delayed_High_Price           = 72
 | 
			
		||||
  fromEnum IBTickType_Delayed_Low_Price            = 73
 | 
			
		||||
  fromEnum IBTickType_Delayed_Volume               = 74
 | 
			
		||||
  fromEnum IBTickType_Delayed_Close                = 75
 | 
			
		||||
  fromEnum IBTickType_Delayed_Open                 = 76
 | 
			
		||||
  fromEnum IBTickType_RT_Trade_Volume              = 77
 | 
			
		||||
  fromEnum IBTickType_Creditman_mark_price         = 78
 | 
			
		||||
  fromEnum IBTickType_Creditman_slow_mark_price    = 79
 | 
			
		||||
  fromEnum IBTickType_Delayed_Bid_Option           = 80
 | 
			
		||||
  fromEnum IBTickType_Delayed_Ask_Option           = 81
 | 
			
		||||
  fromEnum IBTickType_Delayed_Last_Option          = 82
 | 
			
		||||
  fromEnum IBTickType_Delayed_Model_Option         = 83
 | 
			
		||||
  fromEnum IBTickType_Last_Exchange                = 84
 | 
			
		||||
  fromEnum IBTickType_Last_Regulatory_Time         = 85
 | 
			
		||||
  fromEnum IBTickType_Futures_Open_Interest        = 86
 | 
			
		||||
  fromEnum IBTickType_Average_Option_Volume        = 87
 | 
			
		||||
  fromEnum IBTickType_Delayed_Last_Timestamp       = 88
 | 
			
		||||
  fromEnum IBTickType_Shortable_Shares             = 89
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Close                = 92
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Prior_Close          = 93
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Bid                  = 94
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Ask                  = 95
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Last                 = 96
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Frozen_Last          = 97
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_High                 = 98
 | 
			
		||||
  fromEnum IBTickType_ETF_Nav_Low                  = 99
 | 
			
		||||
 | 
			
		||||
type IB_DerivativeSecType = Text
 | 
			
		||||
 | 
			
		||||
data IB_SymbolSample = IB_SymbolSample
 | 
			
		||||
                     { symId           :: Int
 | 
			
		||||
                     , symbol          :: Text
 | 
			
		||||
                     , secType         :: Text
 | 
			
		||||
                     , primaryExchange :: Text
 | 
			
		||||
                     , currency        :: Text
 | 
			
		||||
                     , derivatives     :: [IB_DerivativeSecType]
 | 
			
		||||
                     } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
data IB_IN = IB_PositionData
 | 
			
		||||
           | IB_ManagedAccts { accounts :: [Text] }
 | 
			
		||||
           | IB_NextValidID { orderID :: Int }
 | 
			
		||||
           | IB_ErrorMsg { errorID :: Int, errorCode :: Int, errorMsg :: Text }
 | 
			
		||||
           | IB_AccountValue { key :: Text, value :: Text, currency :: Text, accountName :: Text }
 | 
			
		||||
           | IB_AccountUpdateTime { time :: Text }
 | 
			
		||||
           | IB_PortfolioValue { contract :: IBContract, position :: Float, marketPrice :: Float, marketValue :: Float, averageCost :: Float, unrealizedPNL :: Float, realizedPNL :: Float, accountName :: Text }
 | 
			
		||||
           | IB_SymbolSamples { nextId :: Int, samples :: [IB_SymbolSample] }
 | 
			
		||||
           | IB_MarketDataType { tickerId :: Int, dataType :: IBMarketDataType }
 | 
			
		||||
           | IB_TickReqParams { tickerId :: Int, minTick :: Float, bboExchange :: Text, snapshotPermissions :: Int }
 | 
			
		||||
           | IB_TickPrice { tickerId :: Int, tickType :: IBTickType, price :: Float, size :: Int, attrMask :: Int }
 | 
			
		||||
           | IB_TickSize { tickerId :: Int, fieldId :: Int, size :: Int } -- TODO: field is an enum
 | 
			
		||||
           | IB_TickString { tickerId :: Int, tickType :: IBTickType, content :: Text }
 | 
			
		||||
            deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
voidVersion :: LBS.ByteString -> LBS.ByteString -> Get ()
 | 
			
		||||
voidVersion t v = do
 | 
			
		||||
  version <- getLazyByteStringNul
 | 
			
		||||
  when (version /= v) $ D.trace ("Unexpected Version '" <> LBS8.unpack version <> "' for Message-Type " <> LBS8.unpack t <> ". Expected: '" <> LBS8.unpack v <> "'.") (return ())
 | 
			
		||||
 | 
			
		||||
instance Binary IB_IN where
 | 
			
		||||
  put (IB_ErrorMsg i c m)       = put (IBGenericMessage [IBString "4", IBString "2", IBString $ BS8.pack $ show i, IBString $ BS8.pack $ show c, tToIB m])
 | 
			
		||||
  put (IB_AccountValue k v c n) = put (IBGenericMessage [IBString "6", IBString "2", tToIB k, tToIB v, tToIB c, tToIB n])
 | 
			
		||||
  put (IB_PortfolioValue IBContract{..} p pp v c u r n) = put $ IBGenericMessage [ IBString "7", IBString "8" -- id/version
 | 
			
		||||
                                                                                 , iToIB conId, tToIB symbol, tToIB secType, tToIB lastTradeDate, fToIB strike, tToIB right, tToIB multiplier, tToIB primaryExchange, tToIB currency, tToIB localSymbol, tToIB tradingClass -- contract
 | 
			
		||||
                                                                                 , fToIB p, fToIB pp, fToIB v, fToIB c, fToIB u, fToIB r, tToIB n
 | 
			
		||||
                                                                                 ]
 | 
			
		||||
  put (IB_AccountUpdateTime t)  = put (IBGenericMessage [IBString "8", IBString "1", tToIB t])
 | 
			
		||||
  put (IB_NextValidID v)        = put (IBGenericMessage [IBString "9", IBString "1", IBString $ BS8.pack $ show v])
 | 
			
		||||
  put (IB_ManagedAccts a)       = put (IBGenericMessage [IBString "15", IBArray $ encodeUtf8 <$> a])
 | 
			
		||||
  put  IB_PositionData          = put (IBGenericMessage [IBString "61"])
 | 
			
		||||
  put  IB_SymbolSamples{}       = error "not implemented"
 | 
			
		||||
  put  IB_MarketDataType{}      = error "not implemented"
 | 
			
		||||
  put  IB_TickReqParams{}       = error "not implemented"
 | 
			
		||||
  put  IB_TickPrice{}           = error "not implemented"
 | 
			
		||||
  put  IB_TickSize{}            = error "not implemented"
 | 
			
		||||
  put  IB_TickString{}          = error "not implemented"
 | 
			
		||||
  --put (IB_SymbolSamples r s)    = put (IBGenericMessage [IBString "79", IBString "1", iToIB r, IBArray $ s]) TODO: FIXME
 | 
			
		||||
  
 | 
			
		||||
  get = do 
 | 
			
		||||
    msglen <- getWord32be
 | 
			
		||||
    when (msglen == 0) $ fail "empty message"
 | 
			
		||||
    ident <- return <$> getLazyByteStringNul
 | 
			
		||||
    case ident of
 | 
			
		||||
      Just "1"  -> do
 | 
			
		||||
        voidVersion "1" "6"
 | 
			
		||||
        IB_TickPrice <$> ib2int <*> (toEnum <$> ib2int) <*> ib2f <*> ib2int <*> ib2int
 | 
			
		||||
      Just "2"  -> do
 | 
			
		||||
        voidVersion "2" "6"
 | 
			
		||||
        IB_TickSize <$> ib2int <*> ib2int <*> ib2int
 | 
			
		||||
      Just "4"  -> do
 | 
			
		||||
        voidVersion "4" "2"
 | 
			
		||||
        IB_ErrorMsg <$> ib2int <*> ib2int <*> ib2txt
 | 
			
		||||
      Just "6"  -> do
 | 
			
		||||
        voidVersion "6" "2"
 | 
			
		||||
        IB_AccountValue <$> ib2txt <*> ib2txt <*> ib2txt <*> ib2txt
 | 
			
		||||
      Just "7"  -> do
 | 
			
		||||
        voidVersion "7" "8"
 | 
			
		||||
        c <- IBContract <$> ib2int <*> ib2txt <*> ib2txt <*> ib2txt <*> ib2f <*> ib2txt <*> ib2txt <*> pure "" <*> ib2txt <*> ib2txt <*> ib2txt <*> ib2txt
 | 
			
		||||
        IB_PortfolioValue (c False "" "" "" [] Nothing)  <$> ib2f <*> ib2f <*> ib2f <*> ib2f <*> ib2f <*> ib2f <*> ib2txt
 | 
			
		||||
      Just "8"  -> do
 | 
			
		||||
        voidVersion "8" "1"
 | 
			
		||||
        IB_AccountUpdateTime <$> ib2txt
 | 
			
		||||
      Just "9"  -> do
 | 
			
		||||
        voidVersion "9" "1"
 | 
			
		||||
        IB_NextValidID <$> ib2int
 | 
			
		||||
      Just "15" -> do
 | 
			
		||||
        len <- ib2int
 | 
			
		||||
        IB_ManagedAccts <$> forM [1..len] (const ib2txt)
 | 
			
		||||
      Just "46" -> do
 | 
			
		||||
        voidVersion "46" "6"
 | 
			
		||||
        IB_TickString <$> ib2int <*> (toEnum <$> ib2int) <*> ib2txt
 | 
			
		||||
      Just "58" -> do
 | 
			
		||||
        voidVersion "58" "1"
 | 
			
		||||
        IB_MarketDataType <$> ib2int <*> (toEnum <$> ib2int)
 | 
			
		||||
      Just "61" -> return IB_PositionData
 | 
			
		||||
      Just "79" -> do
 | 
			
		||||
        reqId <- ib2int
 | 
			
		||||
        len <- ib2int
 | 
			
		||||
        symsamples <- forM [1..len] $ const $ do
 | 
			
		||||
          f <- IB_SymbolSample <$> ib2int <*> ib2txt <*> ib2txt <*> ib2txt <*> ib2txt
 | 
			
		||||
          n <- ib2int
 | 
			
		||||
          derivatives <- forM [1..n] $ const ib2txt
 | 
			
		||||
          return $ f derivatives
 | 
			
		||||
        return $ IB_SymbolSamples reqId symsamples
 | 
			
		||||
      Just "81" -> do
 | 
			
		||||
        IB_TickReqParams <$> ib2int <*> ib2f <*> ib2txt <*> ib2int
 | 
			
		||||
      Just x    -> do
 | 
			
		||||
        payload <- getByteString (fromIntegral msglen - (if null ident then 0 else length ident + 1) - 1) -- drop rest of message
 | 
			
		||||
        D.trace ("Payload for "<> LBS8.unpack x <> " not understood: " <> show (IBGenericMessage $ fmap IBString . BS.split 0 . BS.init $ payload)) $ return ()
 | 
			
		||||
        fail $ "unkonwn IB_IN type " <> LBS8.unpack x
 | 
			
		||||
      Nothing   -> fail "Cannot decode Message: no identifier"
 | 
			
		||||
 | 
			
		||||
ib2int :: Get Int
 | 
			
		||||
ib2int = fromMaybe (-1) . readMaybe . LBS8.unpack <$> getLazyByteStringNul
 | 
			
		||||
 | 
			
		||||
ib2f :: Get Float
 | 
			
		||||
ib2f = fromMaybe (-1) . readMaybe . LBS8.unpack <$> getLazyByteStringNul
 | 
			
		||||
 | 
			
		||||
ib2txt :: Get Text
 | 
			
		||||
ib2txt = decodeUtf8 . LBS.toStrict <$> getLazyByteStringNul
 | 
			
		||||
							
								
								
									
										16
									
								
								src/Import.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Import.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
module Import
 | 
			
		||||
  ( module RIO
 | 
			
		||||
  , module Types
 | 
			
		||||
  , module Data.Aeson
 | 
			
		||||
  , module Data.Default
 | 
			
		||||
  , module Text.Show.Pretty
 | 
			
		||||
  , module IBClient.Types
 | 
			
		||||
  ) where
 | 
			
		||||
 | 
			
		||||
import RIO
 | 
			
		||||
import Types
 | 
			
		||||
import Data.Aeson (FromJSON, ToJSON)
 | 
			
		||||
import Data.Default
 | 
			
		||||
import Text.Show.Pretty
 | 
			
		||||
import IBClient.Types
 | 
			
		||||
							
								
								
									
										230
									
								
								src/Run.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								src/Run.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
{-# LANGUAGE OverloadedStrings #-}
 | 
			
		||||
{-# LANGUAGE TypeApplications #-}
 | 
			
		||||
module Run (run) where
 | 
			
		||||
 | 
			
		||||
import Import
 | 
			
		||||
import Chart
 | 
			
		||||
import Types
 | 
			
		||||
import Control.Concurrent
 | 
			
		||||
import Data.Aeson (encodeFile)
 | 
			
		||||
import Data.Bits
 | 
			
		||||
import Data.FingerTree (ViewL(..), ViewR(..), viewl, viewr, split, FingerTree)
 | 
			
		||||
import DearImGui
 | 
			
		||||
import DearImGui.OpenGL3
 | 
			
		||||
import DearImGui.GLFW
 | 
			
		||||
import Graphics.GL
 | 
			
		||||
import Data.StateVar
 | 
			
		||||
import qualified Graphics.UI.GLFW as GLFW
 | 
			
		||||
import qualified Data.Text as T
 | 
			
		||||
import qualified Data.HashMap.Strict as HM
 | 
			
		||||
import qualified Data.FingerTree as FT
 | 
			
		||||
 | 
			
		||||
import IBClient.Connection
 | 
			
		||||
 | 
			
		||||
run :: RIO App ()
 | 
			
		||||
run = do
 | 
			
		||||
  -- set up IB connection & start threads feeding stuff
 | 
			
		||||
 | 
			
		||||
  renderLoop
 | 
			
		||||
 | 
			
		||||
  -- close connections to IB
 | 
			
		||||
 | 
			
		||||
renderLoop :: RIO App ()
 | 
			
		||||
renderLoop = do
 | 
			
		||||
  win <- appWindow <$> ask
 | 
			
		||||
 | 
			
		||||
  liftIO GLFW.pollEvents
 | 
			
		||||
  close <- liftIO $ GLFW.windowShouldClose win
 | 
			
		||||
  if close
 | 
			
		||||
  then do
 | 
			
		||||
    -- save settings & config
 | 
			
		||||
    (w,h)    <- liftIO $ GLFW.getWindowSize win
 | 
			
		||||
    settings <- appSettings <$> ask
 | 
			
		||||
    refs     <- appRefs <$> ask
 | 
			
		||||
    host'    <- liftIO . readTVarIO . twsConnectionRefsHost . twsConnectionRefs $ refs
 | 
			
		||||
    port'    <- liftIO . readTVarIO . twsConnectionRefsPort . twsConnectionRefs $ refs
 | 
			
		||||
    let settings' = settings & windowParams . windowWidth  .~ w
 | 
			
		||||
                             & windowParams . windowHeight .~ h
 | 
			
		||||
                             & twsConnection . host .~ T.pack host'
 | 
			
		||||
                             & twsConnection . port .~ T.pack port'
 | 
			
		||||
    liftIO $ encodeFile "settings.json" settings'
 | 
			
		||||
    logInfo $ display ("Settings Saved" :: Text)
 | 
			
		||||
    -- save cached data
 | 
			
		||||
    
 | 
			
		||||
    logInfo $ display $ T.pack $ ppShow settings'
 | 
			
		||||
  else do
 | 
			
		||||
    refs' <- appRefs <$> ask
 | 
			
		||||
    data' <- appData <$> ask
 | 
			
		||||
    selectedAccount <- readTVarIO $ currentAccount refs'
 | 
			
		||||
    let sendQ = twsConnectionSend $ twsConnectionRefs refs'
 | 
			
		||||
    -- Tell ImGui we're starting a new frame
 | 
			
		||||
    liftIO $ do
 | 
			
		||||
      openGL3NewFrame
 | 
			
		||||
      glfwNewFrame
 | 
			
		||||
      newFrame
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    -- Menu bar
 | 
			
		||||
    withMainMenuBarOpen $ do
 | 
			
		||||
      withMenuOpen "File" $ do
 | 
			
		||||
        menuItem "Quit" >>= \case
 | 
			
		||||
          False -> return ()
 | 
			
		||||
          True  -> liftIO $ GLFW.setWindowShouldClose win True
 | 
			
		||||
      let cr = twsConnectionRefs refs'
 | 
			
		||||
      accs <- fmap HM.keys $ liftIO $ readTVarIO $ Types.accounts data'
 | 
			
		||||
      withComboOpen "Account" (maybe "Select account" T.unpack selectedAccount) $ do
 | 
			
		||||
        forM_ accs $ \a -> do
 | 
			
		||||
          selectable (T.unpack a) >>= \case
 | 
			
		||||
            False -> return ()
 | 
			
		||||
            True  -> do
 | 
			
		||||
              -- cancel subscription of old account (if any)
 | 
			
		||||
              readTVarIO (currentAccount refs') >>= \case
 | 
			
		||||
                Nothing   -> return ()
 | 
			
		||||
                Just aid  -> liftIO $ atomically $ writeTQueue sendQ $ Msg_IB_OUT $ IB_RequestAccountData False aid
 | 
			
		||||
              -- subscribe to new account
 | 
			
		||||
              liftIO $ atomically $ writeTQueue sendQ $ Msg_IB_OUT $ IB_RequestAccountData True a
 | 
			
		||||
              -- finally change
 | 
			
		||||
              liftIO $ atomically $ modifyTVar' (currentAccount refs') (const $ Just a)
 | 
			
		||||
      let cStatus = twsConnectionStatus cr
 | 
			
		||||
      connHost   <- liftIO $ readTVarIO $ twsConnectionRefsHost cr
 | 
			
		||||
      connPort   <- liftIO $ readTVarIO $ twsConnectionRefsPort cr
 | 
			
		||||
      connStatus <- liftIO $ readTVarIO cStatus
 | 
			
		||||
      when (connStatus == TWSDisconnected) $ button "Connect" >>= \case
 | 
			
		||||
        False -> return ()
 | 
			
		||||
        True  -> do
 | 
			
		||||
          if connStatus == TWSDisconnected then do
 | 
			
		||||
            logDebug $ display ("Connecting to TWS on " <> T.pack connHost <> ":" <> T.pack connPort <> "." :: Text)
 | 
			
		||||
            app <- ask
 | 
			
		||||
            void $ liftIO $ forkIO $ forkClient app
 | 
			
		||||
          else do
 | 
			
		||||
            logInfo $ display ("Tried to connect, but we are connected" :: Text)
 | 
			
		||||
            return ()
 | 
			
		||||
      cStatusText <- liftIO $ get (InjetiveGettable cStatus :: InjetiveGettable TWSConnectionStatus String)
 | 
			
		||||
      textColored (InjetiveGettable cStatus :: InjetiveGettable TWSConnectionStatus ImVec4) cStatusText
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    bracket_ (begin "TWS-Connection") end $ do
 | 
			
		||||
      let cr = twsConnectionRefs refs'
 | 
			
		||||
      let cStatus = twsConnectionStatus cr
 | 
			
		||||
      let cHost = twsConnectionRefsHost cr
 | 
			
		||||
      let cPort = twsConnectionRefsPort cr
 | 
			
		||||
      void $ inputText "Host" cHost 255
 | 
			
		||||
      void $ inputText "Port" cPort 255
 | 
			
		||||
      button "Connect" >>= \case
 | 
			
		||||
        False -> return ()
 | 
			
		||||
        True  -> do
 | 
			
		||||
          connStatus <- liftIO $ readTVarIO cStatus
 | 
			
		||||
          connHost   <- liftIO $ readTVarIO cHost
 | 
			
		||||
          connPort   <- liftIO $ readTVarIO cPort
 | 
			
		||||
          if connStatus == TWSDisconnected then do
 | 
			
		||||
            logDebug $ display ("Connecting to TWS on " <> T.pack connHost <> ":" <> T.pack connPort <> "." :: Text)
 | 
			
		||||
            app <- ask
 | 
			
		||||
            void $ liftIO $ forkIO $ forkClient app
 | 
			
		||||
          else do
 | 
			
		||||
            logInfo $ display ("Tried to connect, but we are connected" :: Text)
 | 
			
		||||
            return ()
 | 
			
		||||
      -- TODO: show connection-status
 | 
			
		||||
      cStatusText <- liftIO $ get (InjetiveGettable cStatus :: InjetiveGettable TWSConnectionStatus String)
 | 
			
		||||
      textColored (InjetiveGettable cStatus :: InjetiveGettable TWSConnectionStatus ImVec4) cStatusText
 | 
			
		||||
 | 
			
		||||
    bracket_ (begin "Portfolio") end $ do
 | 
			
		||||
      readTVarIO (currentAccount refs') >>= \case
 | 
			
		||||
        Nothing   -> text "No account selected"
 | 
			
		||||
        Just aid  -> do
 | 
			
		||||
          accs <- liftIO $ readTVarIO $ Types.accounts data'
 | 
			
		||||
          withTable defTableOptions "Portfolio" 6 $ \case
 | 
			
		||||
            False -> return ()
 | 
			
		||||
            True  -> do
 | 
			
		||||
              tableSetupColumn "Symbol"
 | 
			
		||||
              tableSetupColumn "Position"
 | 
			
		||||
              tableSetupColumn "Unrealized Profit"
 | 
			
		||||
              tableSetupColumn "Realized Profit"
 | 
			
		||||
              tableSetupColumn "AVG"
 | 
			
		||||
              tableSetupColumn "Market Value"
 | 
			
		||||
              tableHeadersRow
 | 
			
		||||
              forM_ ((accs HM.! aid) ^.. accountPortfolio . traverse) $ \(IBPortfolioValue c p mp mv ac up rp) -> 
 | 
			
		||||
                do
 | 
			
		||||
                  tableNextRow
 | 
			
		||||
                  whenM tableNextColumn (text $ T.unpack $ localSymbol c)
 | 
			
		||||
                  whenM tableNextColumn (text $ show p)
 | 
			
		||||
                  whenM tableNextColumn (text $ show up)
 | 
			
		||||
                  whenM tableNextColumn (text $ show rp)
 | 
			
		||||
                  whenM tableNextColumn (text $ show mp)
 | 
			
		||||
                  whenM tableNextColumn (text $ show mv)
 | 
			
		||||
 | 
			
		||||
    bracket_ (begin "Search Symbols") end $ do
 | 
			
		||||
      readTVarIO (currentAccount refs') >>= \case
 | 
			
		||||
        Nothing -> text "No account selected"
 | 
			
		||||
        Just _  -> do
 | 
			
		||||
          let nextIDVar = nextValidID data'
 | 
			
		||||
              sLookup   = nextSymbolLookup data'
 | 
			
		||||
          readTVarIO nextIDVar >>= \case
 | 
			
		||||
            Nothing -> text "no id available, waiting ..."
 | 
			
		||||
            Just i  -> do
 | 
			
		||||
              void $ inputText "Symbol-Lookup" (InjetiveGettable @Text @String sLookup) 255
 | 
			
		||||
              button "Lookup" >>= \case
 | 
			
		||||
                False -> return ()
 | 
			
		||||
                True  -> 
 | 
			
		||||
                  liftIO $ atomically $ do
 | 
			
		||||
                    readTVar sLookup >>= writeTQueue sendQ . Msg_IB_OUT . IB_RequestMatchingSymbol i
 | 
			
		||||
                    modifyTVar' nextIDVar (const Nothing)
 | 
			
		||||
          withTable (defTableOptions { tableFlags = ImGuiTableFlags_SortMulti .|. ImGuiTableFlags_Sortable}) "Symbol" 5 $ \case
 | 
			
		||||
            False -> return ()
 | 
			
		||||
            True  -> do
 | 
			
		||||
              tableSetupColumn "Symbol"
 | 
			
		||||
              tableSetupColumn "Security type"
 | 
			
		||||
              tableSetupColumn "Primary exchange"
 | 
			
		||||
              tableSetupColumn "Currency"
 | 
			
		||||
              tableSetupColumn "Available derivatives"
 | 
			
		||||
              withSortableTable $ \(mustSort, sortSpecs) -> do
 | 
			
		||||
                when mustSort $ liftIO $ pPrint sortSpecs
 | 
			
		||||
              tableHeadersRow
 | 
			
		||||
              lResult <- readTVarIO $ symbolLookupResults data'
 | 
			
		||||
              forM_ lResult $ \contract@IBSymbolSample{..} -> do
 | 
			
		||||
                let popupName = "SymbolAction"<>show _symbolId
 | 
			
		||||
                withPopup popupName $ \isPopupOpen -> do
 | 
			
		||||
                  when isPopupOpen $ do
 | 
			
		||||
                    button "creatChart" >>= \case
 | 
			
		||||
                      False -> return ()
 | 
			
		||||
                      True  -> do
 | 
			
		||||
                        logInfo $ display $ "new chart open for: " <> _symbol
 | 
			
		||||
                        newChart $ def { conId = _symbolId, symbol = _symbol, secType = _secType, exchange = "SMART", primaryExchange = _primaryExchange, currency = _currency}
 | 
			
		||||
                let printDatum x = whenM tableNextColumn $ text $ T.unpack x
 | 
			
		||||
                tableNextRow
 | 
			
		||||
                whenM tableNextColumn $ do
 | 
			
		||||
                  void $ selectableWith (defSelectableOptions { flags = ImGuiSelectableFlags_SpanAllColumns }) (T.unpack _symbol)
 | 
			
		||||
                  openPopupOnItemClick popupName ImGuiPopupFlags_MouseButtonRight
 | 
			
		||||
                printDatum _secType
 | 
			
		||||
                printDatum _primaryExchange
 | 
			
		||||
                printDatum _currency
 | 
			
		||||
                printDatum $ T.intercalate ", " _derivatives
 | 
			
		||||
 | 
			
		||||
    -- chart windows
 | 
			
		||||
    charts <- liftIO . readTVarIO . appCharts $ refs'
 | 
			
		||||
    forM_ (HM.toList charts) $ \(symbol, cVar) -> do
 | 
			
		||||
      bracket_ (begin (T.unpack symbol)) end $ do
 | 
			
		||||
        Chart{..} <- liftIO . readTVarIO $ cVar
 | 
			
		||||
        case viewr chartData of
 | 
			
		||||
          EmptyR               -> text "no last price"
 | 
			
		||||
          (_ :> ChartPoint{..}) -> text $ "Last: " <> show pointValue <> "\nTime: " <> show timeOfDay
 | 
			
		||||
        text $ ppShow chartCache
 | 
			
		||||
        text $ ppShow lastCacheUpdate
 | 
			
		||||
        return ()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    -- Show the ImGui demo window
 | 
			
		||||
    showDemoWindow
 | 
			
		||||
 | 
			
		||||
    -- Show the ImPlot demo window
 | 
			
		||||
    --showPlotDemoWindow
 | 
			
		||||
 | 
			
		||||
    -- Render
 | 
			
		||||
    liftIO $ glClear GL_COLOR_BUFFER_BIT
 | 
			
		||||
 | 
			
		||||
    render
 | 
			
		||||
    liftIO $ openGL3RenderDrawData =<< getDrawData
 | 
			
		||||
 | 
			
		||||
    liftIO $ GLFW.swapBuffers win
 | 
			
		||||
 | 
			
		||||
    renderLoop
 | 
			
		||||
							
								
								
									
										240
									
								
								src/Types.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								src/Types.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,240 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
{-# LANGUAGE DeriveAnyClass #-}
 | 
			
		||||
{-# LANGUAGE DerivingStrategies #-}
 | 
			
		||||
{-# LANGUAGE DerivingVia #-}
 | 
			
		||||
{-# LANGUAGE TemplateHaskell #-}
 | 
			
		||||
{-# LANGUAGE TypeApplications #-}
 | 
			
		||||
{-# LANGUAGE TypeOperators #-}
 | 
			
		||||
{-# LANGUAGE UndecidableInstances #-}
 | 
			
		||||
{-# LANGUAGE QuantifiedConstraints #-}
 | 
			
		||||
{-# OPTIONS_GHC -Wno-orphans #-}
 | 
			
		||||
module Types where
 | 
			
		||||
 | 
			
		||||
import Data.Aeson hiding (Options)
 | 
			
		||||
import Data.Default
 | 
			
		||||
import Data.StateVar
 | 
			
		||||
import Data.Types.Injective
 | 
			
		||||
import Data.Time
 | 
			
		||||
import Data.FingerTree
 | 
			
		||||
import Data.Semigroup
 | 
			
		||||
import GHC.Generics
 | 
			
		||||
import Graphics.UI.GLFW (Window)
 | 
			
		||||
import DearImGui
 | 
			
		||||
import RIO
 | 
			
		||||
import RIO.Process
 | 
			
		||||
import Lens.Micro.TH
 | 
			
		||||
import qualified Data.Text as T
 | 
			
		||||
 | 
			
		||||
import IBClient.Types
 | 
			
		||||
 | 
			
		||||
-- | Command line arguments
 | 
			
		||||
data Options = Options
 | 
			
		||||
  { optionsVerbose :: !Bool
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
data WindowParams = WindowParams
 | 
			
		||||
  { _windowHeight :: Int
 | 
			
		||||
  , _windowWidth :: Int
 | 
			
		||||
  } deriving (Show, Generic, FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
instance Default WindowParams where
 | 
			
		||||
  def = WindowParams 1024 768
 | 
			
		||||
 | 
			
		||||
data TWSConnection = TWSConnection
 | 
			
		||||
  { _host :: Text
 | 
			
		||||
  , _port :: Text
 | 
			
		||||
  } deriving (Show, Generic, FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
instance Default TWSConnection where
 | 
			
		||||
  def = TWSConnection "127.0.0.1" "7497"
 | 
			
		||||
 | 
			
		||||
instance FromJSON LogLevel where
 | 
			
		||||
  parseJSON = withText "LogLevel" $ \case
 | 
			
		||||
    "LevelDebug"  -> return LevelDebug
 | 
			
		||||
    "LevelInfo"   -> return LevelInfo
 | 
			
		||||
    "LevelWarn"   -> return LevelWarn
 | 
			
		||||
    "LevelError"  -> return LevelError
 | 
			
		||||
    x -> fail $ T.unpack $ "encountered "<>x
 | 
			
		||||
 | 
			
		||||
instance ToJSON LogLevel where
 | 
			
		||||
  toJSON  LevelDebug    = String "LevelDebug"
 | 
			
		||||
  toJSON  LevelInfo     = String "LevelInfo"
 | 
			
		||||
  toJSON  LevelWarn     = String "LevelWarn"
 | 
			
		||||
  toJSON  LevelError    = String "LevelError"
 | 
			
		||||
  toJSON (LevelOther _) = String "LevelDebug"
 | 
			
		||||
 | 
			
		||||
data Settings = Settings
 | 
			
		||||
  { _windowParams  :: WindowParams
 | 
			
		||||
  , _twsConnection :: TWSConnection
 | 
			
		||||
  , _logLevel      :: LogLevel
 | 
			
		||||
  } deriving (Show, Generic, FromJSON, ToJSON)
 | 
			
		||||
 | 
			
		||||
makeLenses ''WindowParams
 | 
			
		||||
makeLenses ''TWSConnection
 | 
			
		||||
makeLenses ''Settings
 | 
			
		||||
 | 
			
		||||
instance Default Settings where
 | 
			
		||||
  def = Settings def def LevelWarn
 | 
			
		||||
 | 
			
		||||
data TWSConnectionStatus = TWSDisconnected
 | 
			
		||||
                         | TWSConnecting
 | 
			
		||||
                         | TWSConnected
 | 
			
		||||
                         deriving (Show, Eq, Enum, Bounded)
 | 
			
		||||
 | 
			
		||||
data TWSConnectionRefs = TWSConnectionRefs
 | 
			
		||||
  { twsConnectionRefsHost :: TVar String
 | 
			
		||||
  , twsConnectionRefsPort :: TVar String
 | 
			
		||||
  , twsConnectionStatus   :: TVar TWSConnectionStatus
 | 
			
		||||
  , twsConnectionSend     :: TQueue Msg_IB_OUT
 | 
			
		||||
  , twsConnectionRecieve  :: TQueue Msg_IB_IN
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
instance Injective TWSConnectionStatus ImVec4 where
 | 
			
		||||
  to = \case
 | 
			
		||||
      TWSDisconnected -> ImVec4 1 0 0 1
 | 
			
		||||
      TWSConnecting   -> ImVec4 1 1 0 1
 | 
			
		||||
      TWSConnected    -> ImVec4 0 1 0 1
 | 
			
		||||
 | 
			
		||||
instance Injective TWSConnectionStatus String where
 | 
			
		||||
  to = \case
 | 
			
		||||
      TWSDisconnected -> "Not Connected"
 | 
			
		||||
      TWSConnecting   -> "Trying to connect..."
 | 
			
		||||
      TWSConnected    -> "Connected"
 | 
			
		||||
 | 
			
		||||
data DataRefs = DataRefs
 | 
			
		||||
              { accounts            :: TVar (HashMap Text IBAccount)
 | 
			
		||||
              , nextValidID         :: TVar (Maybe Int)
 | 
			
		||||
              , nextSymbolLookup    :: TVar Text
 | 
			
		||||
              , symbolLookupResults :: TVar [IBSymbolSample]
 | 
			
		||||
              }
 | 
			
		||||
mkIBAccount :: Text -> IBAccount
 | 
			
		||||
mkIBAccount u = IBAccount (IBAccountInfo u mempty mempty) mempty mempty
 | 
			
		||||
 | 
			
		||||
data IBAccount = IBAccount
 | 
			
		||||
               { _accountInfo       :: IBAccountInfo
 | 
			
		||||
               , _accountPortfolio  :: [IBPortfolioValue]
 | 
			
		||||
               , _accountStrategies :: [IBAccountStrategy]
 | 
			
		||||
               } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
data IBAccountInfo = IBAccountInfo
 | 
			
		||||
                   { _accountName       :: Text
 | 
			
		||||
                   , _accountProperties :: HashMap Text [(Text, Text)] -- (value, currency)
 | 
			
		||||
                   , _accountLastUpdate :: Text
 | 
			
		||||
                   } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
data IBPortfolioValue = IBPortfolioValue
 | 
			
		||||
                      { _contract      :: IBContract
 | 
			
		||||
                      , _position      :: Float
 | 
			
		||||
                      , _marketPrice   :: Float
 | 
			
		||||
                      , _marketValue   :: Float
 | 
			
		||||
                      , _averageCost   :: Float
 | 
			
		||||
                      , _unrealizedPNL :: Float
 | 
			
		||||
                      , _realizedPNL   :: Float
 | 
			
		||||
                      } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
newtype IBAccountStrategy = IBAccountStrategy [Int] -- Int -> contract-id for this strategy
 | 
			
		||||
  deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
data IBSymbolSample = IBSymbolSample
 | 
			
		||||
                    { _symbolId :: Int
 | 
			
		||||
                    , _symbol :: Text
 | 
			
		||||
                    , _secType :: Text
 | 
			
		||||
                    , _primaryExchange :: Text
 | 
			
		||||
                    , _currency :: Text
 | 
			
		||||
                    , _derivatives :: [Text]
 | 
			
		||||
                    } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
makeLenses ''IBAccountStrategy
 | 
			
		||||
makeLenses ''IBAccountInfo
 | 
			
		||||
makeLenses ''IBAccount
 | 
			
		||||
 | 
			
		||||
data ChartSettings = ChartSettings
 | 
			
		||||
                   { chartResolution :: Int
 | 
			
		||||
                   , chartStart      :: Maybe UTCTime
 | 
			
		||||
                   , chartEnd        :: Maybe UTCTime
 | 
			
		||||
                   } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
defChartSettings :: ChartSettings
 | 
			
		||||
defChartSettings = ChartSettings 60 Nothing Nothing
 | 
			
		||||
 | 
			
		||||
-- data TimeWindow = TimeWindow
 | 
			
		||||
--                 { begin :: Int
 | 
			
		||||
--                 , end :: Int
 | 
			
		||||
--                 } deriving (Show, Eq)
 | 
			
		||||
--
 | 
			
		||||
-- instance Semigroup TimeWindow where
 | 
			
		||||
--   (TimeWindow a b) <> (TimeWindow x y) = TimeWindow (min a x) (max b y)
 | 
			
		||||
--
 | 
			
		||||
-- instance Monoid TimeWindow where
 | 
			
		||||
--   mempty = TimeWindow 0 86400
 | 
			
		||||
 | 
			
		||||
newtype TimePoint = TimePoint Int
 | 
			
		||||
  deriving Eq
 | 
			
		||||
  deriving newtype Show
 | 
			
		||||
  deriving (Semigroup, Monoid) via (Max Int)
 | 
			
		||||
 | 
			
		||||
data ChartStudies = SMA { window :: Int, value :: Float }
 | 
			
		||||
                  | OLHC { olhc_open :: Float, olhc_low :: Float, olhc_high :: Float, olhc_close :: Float}
 | 
			
		||||
                  deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
data ChartPoint = ChartPoint
 | 
			
		||||
                { timeOfDay  :: TimePoint
 | 
			
		||||
                , pointValue :: Float
 | 
			
		||||
                , pointExtra :: [ChartStudies]
 | 
			
		||||
                } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
instance Measured TimePoint ChartPoint where
 | 
			
		||||
  measure = timeOfDay
 | 
			
		||||
 | 
			
		||||
data Chart = Chart
 | 
			
		||||
           { chartData       :: FingerTree TimePoint ChartPoint
 | 
			
		||||
           , chartHistData   :: HashMap Day (FingerTree TimePoint ChartPoint)
 | 
			
		||||
           , fillerThread    :: ThreadId
 | 
			
		||||
           , chartSettings   :: ChartSettings
 | 
			
		||||
           , chartCache      :: [ChartPoint]
 | 
			
		||||
           , lastCacheUpdate :: Maybe TimePoint
 | 
			
		||||
           , chartDirty      :: Bool
 | 
			
		||||
           } deriving (Show, Eq)
 | 
			
		||||
 | 
			
		||||
newtype InjetiveGettable a b = InjetiveGettable
 | 
			
		||||
                          { gettable :: TVar a
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
instance (Injective a b) => HasGetter (InjetiveGettable a b) b where
 | 
			
		||||
  get r = liftIO $ do
 | 
			
		||||
    (value :: a) <- get (gettable r)
 | 
			
		||||
    return $ Data.Types.Injective.to value
 | 
			
		||||
 | 
			
		||||
instance (Injective b a) => HasSetter (InjetiveGettable a b) b where
 | 
			
		||||
  t $= a = liftIO $ do
 | 
			
		||||
    let b = Data.Types.Injective.to a
 | 
			
		||||
    gettable t $= b
 | 
			
		||||
 | 
			
		||||
newtype DefaultJSON a = DefaultJSON { unDefaultJSON :: a }
 | 
			
		||||
 | 
			
		||||
instance (FromJSON a, Default a) => FromJSON (DefaultJSON a) where
 | 
			
		||||
    parseJSON v = DefaultJSON <$> (parseJSON v <|> pure def)
 | 
			
		||||
 | 
			
		||||
data AppRefs = AppRefs
 | 
			
		||||
  { twsConnectionRefs :: TWSConnectionRefs
 | 
			
		||||
  , currentAccount    :: TVar (Maybe Text)
 | 
			
		||||
  , appCharts         :: TVar (HashMap Text (TVar Chart))
 | 
			
		||||
  , tickerIdToSymbol  :: TVar (HashMap Int Text)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
data App = App
 | 
			
		||||
  { appSettings :: !Settings
 | 
			
		||||
  , appLogFunc :: !LogFunc
 | 
			
		||||
  , appProcessContext :: !ProcessContext
 | 
			
		||||
  , appOptions :: !Options
 | 
			
		||||
  , appWindow :: !Window
 | 
			
		||||
  , appRefs :: !AppRefs
 | 
			
		||||
  , appData :: !DataRefs
 | 
			
		||||
  -- Add other app-specific configuration information here
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
instance HasLogFunc App where
 | 
			
		||||
  logFuncL = lens appLogFunc (\x y -> x { appLogFunc = y })
 | 
			
		||||
instance HasProcessContext App where
 | 
			
		||||
  processContextL = lens appProcessContext (\x y -> x { appProcessContext = y })
 | 
			
		||||
							
								
								
									
										11
									
								
								src/Util.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Util.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
-- | Silly utility module, used to demonstrate how to write a test
 | 
			
		||||
-- case.
 | 
			
		||||
module Util
 | 
			
		||||
  ( plus2
 | 
			
		||||
  ) where
 | 
			
		||||
 | 
			
		||||
import RIO
 | 
			
		||||
 | 
			
		||||
plus2 :: Int -> Int
 | 
			
		||||
plus2 = (+ 2)
 | 
			
		||||
							
								
								
									
										79
									
								
								stack.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								stack.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
# This file was automatically generated by 'stack init'
 | 
			
		||||
#
 | 
			
		||||
# Some commonly used options have been documented as comments in this file.
 | 
			
		||||
# For advanced use and comprehensive documentation of the format, please see:
 | 
			
		||||
# https://docs.haskellstack.org/en/stable/yaml_configuration/
 | 
			
		||||
 | 
			
		||||
# Resolver to choose a 'specific' stackage snapshot or a compiler version.
 | 
			
		||||
# A snapshot resolver dictates the compiler version and the set of packages
 | 
			
		||||
# to be used for project dependencies. For example:
 | 
			
		||||
#
 | 
			
		||||
# resolver: lts-3.5
 | 
			
		||||
# resolver: nightly-2015-09-21
 | 
			
		||||
# resolver: ghc-7.10.2
 | 
			
		||||
#
 | 
			
		||||
# The location of a snapshot can be provided as a file or url. Stack assumes
 | 
			
		||||
# a snapshot provided as a file might change, whereas a url resource does not.
 | 
			
		||||
#
 | 
			
		||||
# resolver: ./custom-snapshot.yaml
 | 
			
		||||
# resolver: https://example.com/snapshots/2018-01-01.yaml
 | 
			
		||||
resolver: lts-18.24
 | 
			
		||||
 | 
			
		||||
# User packages to be built.
 | 
			
		||||
# Various formats can be used as shown in the example below.
 | 
			
		||||
#
 | 
			
		||||
# packages:
 | 
			
		||||
# - some-directory
 | 
			
		||||
# - https://example.com/foo/bar/baz-0.0.2.tar.gz
 | 
			
		||||
#   subdirs:
 | 
			
		||||
#   - auto-update
 | 
			
		||||
#   - wai
 | 
			
		||||
packages:
 | 
			
		||||
- .
 | 
			
		||||
- deps/dear-imgui.hs
 | 
			
		||||
# Dependency packages to be pulled from upstream that are not in the resolver.
 | 
			
		||||
# These entries can reference officially published versions as well as
 | 
			
		||||
# forks / in-progress versions pinned to a git hash. For example:
 | 
			
		||||
#
 | 
			
		||||
# extra-deps:
 | 
			
		||||
# - acme-missiles-0.3
 | 
			
		||||
# - git: https://github.com/commercialhaskell/stack.git
 | 
			
		||||
#   commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
 | 
			
		||||
#
 | 
			
		||||
extra-deps:
 | 
			
		||||
  - type-iso-1.0.1.0@sha256:75682a06a5af1798c6641ba3cc175685a1f699962ad22ab194a487c0d6b7da66,1892
 | 
			
		||||
  - numericpeano-0.2.0.0@sha256:e3a1dc960817a81f39d276e7bfa0124e8efa1b91b5c272a70dfa16c38627f172,1406
 | 
			
		||||
 | 
			
		||||
allow-newer: true
 | 
			
		||||
 | 
			
		||||
# Override default flag values for local packages and extra-deps
 | 
			
		||||
flags:
 | 
			
		||||
    dear-imgui:
 | 
			
		||||
      # libraries
 | 
			
		||||
      glfw: true
 | 
			
		||||
      sdl: false
 | 
			
		||||
      vulkan: false
 | 
			
		||||
      # hardware-requirements
 | 
			
		||||
      opengl3: true
 | 
			
		||||
      opengl2: false
 | 
			
		||||
 | 
			
		||||
# Extra package databases containing global packages
 | 
			
		||||
# extra-package-dbs: []
 | 
			
		||||
 | 
			
		||||
# Control whether we use the GHC we find on the path
 | 
			
		||||
# system-ghc: true
 | 
			
		||||
#
 | 
			
		||||
# Require a specific version of stack, using version ranges
 | 
			
		||||
# require-stack-version: -any # Default
 | 
			
		||||
# require-stack-version: ">=2.7"
 | 
			
		||||
#
 | 
			
		||||
# Override the architecture used by stack, especially useful on Windows
 | 
			
		||||
# arch: i386
 | 
			
		||||
# arch: x86_64
 | 
			
		||||
#
 | 
			
		||||
# Extra directories used by stack for building
 | 
			
		||||
# extra-include-dirs: [/path/to/dir]
 | 
			
		||||
# extra-lib-dirs: [/path/to/dir]
 | 
			
		||||
#
 | 
			
		||||
# Allow a newer minor version of GHC than the snapshot specifies
 | 
			
		||||
# compiler-check: newer-minor
 | 
			
		||||
							
								
								
									
										1
									
								
								test/Spec.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/Spec.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
 | 
			
		||||
							
								
								
									
										14
									
								
								test/UtilSpec.hs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								test/UtilSpec.hs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
{-# LANGUAGE NoImplicitPrelude #-}
 | 
			
		||||
module UtilSpec (spec) where
 | 
			
		||||
 | 
			
		||||
import Import
 | 
			
		||||
import Util
 | 
			
		||||
import Test.Hspec
 | 
			
		||||
import Test.Hspec.QuickCheck
 | 
			
		||||
 | 
			
		||||
spec :: Spec
 | 
			
		||||
spec = do
 | 
			
		||||
  describe "plus2" $ do
 | 
			
		||||
    it "basic check" $ plus2 0 `shouldBe` 2
 | 
			
		||||
    it "overflow" $ plus2 maxBound `shouldBe` minBound + 1
 | 
			
		||||
    prop "minus 2" $ \i -> plus2 i - 2 `shouldBe` i
 | 
			
		||||
		Reference in New Issue
	
	Block a user