bbgo_origin/pkg/config/loader.go

249 lines
5.2 KiB
Go
Raw Normal View History

2020-10-20 06:32:22 +00:00
package config
import (
"encoding/json"
"io/ioutil"
"reflect"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"github.com/c9s/bbgo/pkg/bbgo"
)
type SingleExchangeStrategyConfig struct {
Mounts []string
Strategy bbgo.SingleExchangeStrategy
}
type StringSlice []string
2020-10-23 06:01:45 +00:00
func (s *StringSlice) decode(a interface{}) error {
switch d := a.(type) {
case string:
*s = append(*s, d)
case []string:
*s = append(*s, d...)
2020-10-23 06:01:45 +00:00
case []interface{}:
for _, de := range d {
if err := s.decode(de); err != nil {
return err
}
}
default:
2020-10-23 06:01:45 +00:00
return errors.Errorf("unexpected type %T for StringSlice: %+v", d, d)
}
return nil
}
func (s *StringSlice) UnmarshalJSON(b []byte) error {
var a interface{}
var err = json.Unmarshal(b, &a)
if err != nil {
return err
}
2020-10-23 06:01:45 +00:00
return s.decode(a)
}
type PnLReporter struct {
AverageCostBySymbols StringSlice `json:"averageCostBySymbols"`
Of StringSlice `json:"of" yaml:"of"`
When StringSlice `json:"when" yaml:"when"`
}
2020-10-20 06:32:22 +00:00
type Config struct {
2020-10-23 06:13:16 +00:00
Imports []string `json:"imports" yaml:"imports"`
ExchangeStrategies []SingleExchangeStrategyConfig
CrossExchangeStrategies []bbgo.CrossExchangeStrategy
2020-10-23 06:13:16 +00:00
PnLReporters []PnLReporter `json:"reportPnL" yaml:"reportPnL"`
2020-10-20 06:32:22 +00:00
}
type Stash map[string]interface{}
2020-10-23 06:49:54 +00:00
func loadStash(config []byte) (Stash, error) {
2020-10-20 06:32:22 +00:00
stash := make(Stash)
if err := yaml.Unmarshal(config, stash); err != nil {
return nil, err
}
2020-10-23 06:49:54 +00:00
return stash, nil
2020-10-20 06:32:22 +00:00
}
func Load(configFile string) (*Config, error) {
var config Config
2020-10-23 06:49:54 +00:00
content, err := ioutil.ReadFile(configFile)
2020-10-20 06:32:22 +00:00
if err != nil {
return nil, err
}
2020-10-23 06:49:54 +00:00
stash, err := loadStash(content)
if err != nil {
return nil, err
}
if err := loadImports(&config, stash); err != nil {
return nil, err
}
if err := loadExchangeStrategies(&config, stash); err != nil {
2020-10-20 06:32:22 +00:00
return nil, err
}
if err := loadCrossExchangeStrategies(&config, stash); err != nil {
return nil, err
}
if err := loadReportPnL(&config, stash); err != nil {
return nil, err
}
2020-10-20 06:32:22 +00:00
return &config, nil
}
2020-10-23 06:49:54 +00:00
func loadImports(config *Config, stash Stash) error {
importStash, ok := stash["imports"]
if !ok {
return nil
}
imports, err := reUnmarshal(importStash, &config.Imports)
if err != nil {
return err
}
config.Imports = *imports.(*[]string)
return nil
}
func loadReportPnL(config *Config, stash Stash) error {
reporterStash, ok := stash["reportPnL"]
if !ok {
return nil
}
reporters, err := reUnmarshal(reporterStash, &config.PnLReporters)
if err != nil {
return err
}
config.PnLReporters = *(reporters.(*[]PnLReporter))
return nil
}
func loadCrossExchangeStrategies(config *Config, stash Stash) (err error) {
exchangeStrategiesConf, ok := stash["crossExchangeStrategies"]
2020-10-20 06:32:22 +00:00
if !ok {
return nil
2020-10-20 06:32:22 +00:00
}
2020-10-23 06:49:54 +00:00
if len(bbgo.LoadedCrossExchangeStrategies) == 0 {
return errors.New("no cross exchange strategy is registered")
}
configList, ok := exchangeStrategiesConf.([]interface{})
2020-10-20 06:32:22 +00:00
if !ok {
return errors.New("expecting list in crossExchangeStrategies")
2020-10-20 06:32:22 +00:00
}
for _, entry := range configList {
configStash, ok := entry.(Stash)
2020-10-20 06:32:22 +00:00
if !ok {
return errors.Errorf("strategy config should be a map, given: %T %+v", entry, entry)
2020-10-20 06:32:22 +00:00
}
for id, conf := range configStash {
// look up the real struct type
if st, ok := bbgo.LoadedExchangeStrategies[id]; ok {
val, err := reUnmarshal(conf, st)
if err != nil {
return err
}
config.CrossExchangeStrategies = append(config.CrossExchangeStrategies, val.(bbgo.CrossExchangeStrategy))
}
}
}
return nil
}
func loadExchangeStrategies(config *Config, stash Stash) (err error) {
exchangeStrategiesConf, ok := stash["exchangeStrategies"]
if !ok {
return nil
}
2020-10-23 06:49:54 +00:00
if len(bbgo.LoadedExchangeStrategies) == 0 {
return errors.New("no exchange strategy is registered")
}
configList, ok := exchangeStrategiesConf.([]interface{})
if !ok {
return errors.New("expecting list in exchangeStrategies")
}
for _, entry := range configList {
configStash, ok := entry.(Stash)
if !ok {
return errors.Errorf("strategy config should be a map, given: %T %+v", entry, entry)
}
var mounts []string
if val, ok := configStash["on"]; ok {
if values, ok := val.([]string); ok {
mounts = append(mounts, values...)
} else if str, ok := val.(string); ok {
mounts = append(mounts, str)
}
}
for id, conf := range configStash {
// look up the real struct type
if st, ok := bbgo.LoadedExchangeStrategies[id]; ok {
val, err := reUnmarshal(conf, st)
if err != nil {
return err
}
config.ExchangeStrategies = append(config.ExchangeStrategies, SingleExchangeStrategyConfig{
Mounts: mounts,
Strategy: val.(bbgo.SingleExchangeStrategy),
})
2020-10-20 06:32:22 +00:00
}
}
}
return nil
2020-10-20 06:32:22 +00:00
}
func reUnmarshal(conf interface{}, tpe interface{}) (interface{}, error) {
// get the type "*Strategy"
rt := reflect.TypeOf(tpe)
// allocate new object from the given type
val := reflect.New(rt)
// now we have &(*Strategy) -> **Strategy
valRef := val.Interface()
plain, err := json.Marshal(conf)
if err != nil {
return nil, err
}
if err := json.Unmarshal(plain, valRef); err != nil {
return nil, errors.Wrapf(err, "json parsing error, given payload: %s", plain)
}
return val.Elem().Interface(), nil
}