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"
|
|
|
|
)
|
|
|
|
|
2020-10-21 07:49:43 +00:00
|
|
|
type SingleExchangeStrategyConfig struct {
|
|
|
|
Mounts []string
|
|
|
|
Strategy bbgo.SingleExchangeStrategy
|
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
type StringSlice []string
|
|
|
|
|
|
|
|
func (s *StringSlice) UnmarshalJSON(b []byte) (err error) {
|
|
|
|
var a interface{}
|
|
|
|
err = json.Unmarshal(b, &a)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch d := a.(type) {
|
|
|
|
case string:
|
|
|
|
*s = append(*s, d)
|
|
|
|
|
|
|
|
case []string:
|
|
|
|
*s = append(*s, d...)
|
|
|
|
|
|
|
|
default:
|
|
|
|
err = errors.New("unexpected type for StringSlice")
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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-21 07:49:43 +00:00
|
|
|
ExchangeStrategies []SingleExchangeStrategyConfig
|
|
|
|
CrossExchangeStrategies []bbgo.CrossExchangeStrategy
|
2020-10-23 05:50:17 +00:00
|
|
|
|
|
|
|
PnLReporters []PnLReporter `json:"reportPnL"`
|
2020-10-20 06:32:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Stash map[string]interface{}
|
|
|
|
|
|
|
|
func loadStash(configFile string) (Stash, error) {
|
|
|
|
config, err := ioutil.ReadFile(configFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
stash := make(Stash)
|
|
|
|
if err := yaml.Unmarshal(config, stash); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return stash, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func Load(configFile string) (*Config, error) {
|
|
|
|
var config Config
|
|
|
|
|
|
|
|
stash, err := loadStash(configFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
if err := loadExchangeStrategies(&config, stash); err != nil {
|
2020-10-20 06:32:22 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
if err := loadCrossExchangeStrategies(&config, stash); err != nil {
|
2020-10-21 07:49:43 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
if err := loadReportPnL(&config, stash); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-10-21 07:49:43 +00:00
|
|
|
|
2020-10-20 06:32:22 +00:00
|
|
|
return &config, nil
|
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
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) {
|
2020-10-21 07:49:43 +00:00
|
|
|
exchangeStrategiesConf, ok := stash["crossExchangeStrategies"]
|
2020-10-20 06:32:22 +00:00
|
|
|
if !ok {
|
2020-10-23 05:50:17 +00:00
|
|
|
return nil
|
2020-10-20 06:32:22 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 07:49:43 +00:00
|
|
|
configList, ok := exchangeStrategiesConf.([]interface{})
|
2020-10-20 06:32:22 +00:00
|
|
|
if !ok {
|
2020-10-23 05:50:17 +00:00
|
|
|
return errors.New("expecting list in crossExchangeStrategies")
|
2020-10-20 06:32:22 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 07:49:43 +00:00
|
|
|
for _, entry := range configList {
|
|
|
|
configStash, ok := entry.(Stash)
|
2020-10-20 06:32:22 +00:00
|
|
|
if !ok {
|
2020-10-23 05:50:17 +00:00
|
|
|
return errors.Errorf("strategy config should be a map, given: %T %+v", entry, entry)
|
2020-10-20 06:32:22 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 07:49:43 +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 {
|
2020-10-23 05:50:17 +00:00
|
|
|
return err
|
2020-10-21 07:49:43 +00:00
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
config.CrossExchangeStrategies = append(config.CrossExchangeStrategies, val.(bbgo.CrossExchangeStrategy))
|
2020-10-20 07:54:32 +00:00
|
|
|
}
|
2020-10-21 07:49:43 +00:00
|
|
|
}
|
|
|
|
}
|
2020-10-20 07:54:32 +00:00
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
return nil
|
2020-10-21 07:49:43 +00:00
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
func loadExchangeStrategies(config *Config, stash Stash) (err error) {
|
2020-10-21 07:49:43 +00:00
|
|
|
exchangeStrategiesConf, ok := stash["exchangeStrategies"]
|
|
|
|
if !ok {
|
2020-10-23 05:50:17 +00:00
|
|
|
return nil
|
2020-10-21 07:49:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
configList, ok := exchangeStrategiesConf.([]interface{})
|
|
|
|
if !ok {
|
2020-10-23 05:50:17 +00:00
|
|
|
return errors.New("expecting list in exchangeStrategies")
|
2020-10-21 07:49:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range configList {
|
|
|
|
configStash, ok := entry.(Stash)
|
|
|
|
if !ok {
|
2020-10-23 05:50:17 +00:00
|
|
|
return errors.Errorf("strategy config should be a map, given: %T %+v", entry, entry)
|
2020-10-21 07:49:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2020-10-23 05:50:17 +00:00
|
|
|
return err
|
2020-10-20 07:54:32 +00:00
|
|
|
}
|
2020-10-21 07:49:43 +00:00
|
|
|
|
2020-10-23 05:50:17 +00:00
|
|
|
config.ExchangeStrategies = append(config.ExchangeStrategies, SingleExchangeStrategyConfig{
|
2020-10-21 07:49:43 +00:00
|
|
|
Mounts: mounts,
|
|
|
|
Strategy: val.(bbgo.SingleExchangeStrategy),
|
|
|
|
})
|
2020-10-20 06:32:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-23 05:50:17 +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
|
|
|
|
}
|